Full Code of TypeError/secure.py for AI

main 0d90d71d6843 cached
80 files
340.1 KB
78.5k tokens
510 symbols
1 requests
Download .txt
Showing preview only (363K chars total). Download the full file or copy to clipboard to get everything.
Repository: TypeError/secure.py
Branch: main
Commit: 0d90d71d6843
Files: 80
Total size: 340.1 KB

Directory structure:
gitextract___lgw2bt/

├── .github/
│   └── workflows/
│       └── tests.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── README.md
│   ├── configuration.md
│   ├── frameworks.md
│   ├── headers/
│   │   ├── cache_control.md
│   │   ├── content_security_policy.md
│   │   ├── cross-origin-resource-policy.md
│   │   ├── cross_origin_embedder_policy.md
│   │   ├── cross_origin_opener_policy.md
│   │   ├── custom_header.md
│   │   ├── dns_prefetch_control.md
│   │   ├── permissions_policy.md
│   │   ├── referrer_policy.md
│   │   ├── server.md
│   │   ├── strict_transport_security.md
│   │   ├── x-permitted-cross-domain-policies.md
│   │   ├── x_content_type_options.md
│   │   └── x_frame_options.md
│   ├── installation.md
│   ├── migration.md
│   ├── security_considerations.md
│   └── usage.md
├── pyproject.toml
├── secure/
│   ├── __init__.py
│   ├── _internal/
│   │   ├── __init__.py
│   │   ├── configured_headers.py
│   │   ├── constants.py
│   │   ├── emit.py
│   │   ├── normalize.py
│   │   ├── policy.py
│   │   ├── presets.py
│   │   └── types.py
│   ├── headers/
│   │   ├── __init__.py
│   │   ├── _validation.py
│   │   ├── base_header.py
│   │   ├── cache_control.py
│   │   ├── content_security_policy.py
│   │   ├── cross_origin_embedder_policy.py
│   │   ├── cross_origin_opener_policy.py
│   │   ├── cross_origin_resource_policy.py
│   │   ├── custom_header.py
│   │   ├── permissions_policy.py
│   │   ├── referrer_policy.py
│   │   ├── server.py
│   │   ├── strict_transport_security.py
│   │   ├── x_content_type_options.py
│   │   ├── x_dns_prefetch_control.py
│   │   ├── x_frame_options.py
│   │   └── x_permitted_cross_domain_policies.py
│   ├── middleware/
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   └── wsgi.py
│   ├── py.typed
│   └── secure.py
└── tests/
    ├── __init__.py
    ├── headers/
    │   ├── test_cache_control.py
    │   ├── test_content_security_policy.py
    │   ├── test_cross_origin_embedder_policy.py
    │   ├── test_cross_origin_opener_policy.py
    │   ├── test_custom_header.py
    │   ├── test_header_contracts_extended.py
    │   ├── test_permissions_policy.py
    │   ├── test_referrer_policy.py
    │   ├── test_server.py
    │   ├── test_strict_transport_security.py
    │   ├── test_x_content_type_options.py
    │   └── test_x_frame_options.py
    ├── middleware/
    │   ├── test_middleware.py
    │   └── test_secure_protocols.py
    └── secure_tests/
        ├── __init__.py
        ├── test_exports.py
        ├── test_headers.py
        ├── test_internal_helpers.py
        └── test_secure.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/tests.yml
================================================
name: Unit Tests

on:
  pull_request:

permissions:
  contents: read

jobs:
  tests:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python:
          - "3.10"
          - "3.11"
          - "3.12"
          - "3.13"
          - "3.14-dev"

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}

      - name: Install toolchain
        run: pip install ruff==0.13.2 pytest

      - name: Install package
        run: pip install -e .

      - name: Unit tests
        run: python -m pytest

      - name: Lint
        run: ruff check secure tests


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea

.DS_Store

================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

- Placeholder for upcoming changes.

## [2.0.1] - 2026-04-21

This is the first stable v2 release. Version `2.0.0` was burned and should be skipped when tagging or publishing.

v2 focuses on a cleaner public API, a redesigned preset model, first-class ASGI/WSGI middleware, and stricter, safer header handling.

### Breaking Changes

- `Secure.headers` is now strict and read-only
  - v1: `headers` was a cached `dict[str, str]` and silently collapsed duplicate names
  - v2: duplicate header names (case-insensitive) raise `ValueError`
  - use `header_items()` for multi-valued output or `deduplicate_headers()` to resolve duplicates

- Default headers have changed
  - `Secure.with_default_headers()` now maps to `Preset.BALANCED`
  - v1 default included `Cache-Control: no-store`; v2 does not
  - applications relying on v1 defaults should explicitly configure required headers

- Presets redesigned
  - new `Preset.BALANCED` (recommended default)
  - `Preset.BASIC` updated for Helmet.js parity and no longer matches v1 BASIC
  - `Preset.STRICT` no longer enables HSTS preload by default
  - cache behavior and header composition differ across presets compared to v1

- FastAPI / ASGI integration model changed in practice
  - v1 relied on per-response mutation (`set_headers` / `set_headers_async`)
  - v2 introduces middleware-based integration as the recommended approach

### Added

- Middleware
  - `SecureASGIMiddleware`
  - `SecureWSGIMiddleware`
  - `secure.middleware` module

- Header pipeline helpers
  - `allowlist_headers(...)`
  - `deduplicate_headers(...)`
  - `validate_and_normalize_headers(...)`
  - `header_items()` for ordered `(name, value)` output

- New header builders and constants
  - `CrossOriginResourcePolicy`
  - `XDnsPrefetchControl`
  - `XPermittedCrossDomainPolicies`
  - `MULTI_OK`, `COMMA_JOIN_OK`, `DEFAULT_ALLOWED_HEADERS`
  - policy enums: `OnInvalidPolicy`, `OnUnexpectedPolicy`, `DeduplicateAction`

### Changed

- `Secure.with_default_headers()` now returns the balanced preset
- Header handling is stricter and fails fast on invalid or duplicate configurations
- Header normalization and validation are first-class operations
- Response integration is more robust across sync and async frameworks
- `headers_list` mutations are now reflected correctly (no stale cached state)
- Documentation updated to emphasize middleware usage and preset selection

### Migration Notes

- Do not assume v1 defaults
  - compare emitted headers and explicitly configure any required behavior

- Audit any usage of `Secure.headers`
  - treat as read-only in v2
  - use `header_items()` or `deduplicate_headers()` when duplicates are possible

- Move to middleware for ASGI/WSGI apps
  - replace per-response `set_headers_async()` calls with `SecureASGIMiddleware` or `SecureWSGIMiddleware`

- Explicitly configure behavior that changed
  - add `Cache-Control` if you relied on v1 defaults
  - add HSTS preload manually if required

### Notes

- Neither v1 nor v2 exposes `secure.__version__`; use package metadata for version checks

## [1.0.1] - 2024-10-18

### Fixed

- Improved performance of `Secure.set_headers` by reducing redundant type checks. ([#26](https://github.com/TypeError/secure/issues/26))

## [1.0.0] - 2024-09-27

### Breaking Changes

- Full redesign of the `secure.py` library with modern Python (3.10+) support.
- Major API overhaul for improved usability and Pythonic design.

### Added

- Enhanced support for FastAPI and asynchronous frameworks.
- Added type hints and better type annotations for a smoother developer experience.
- Refined default security headers for improved protection across web frameworks.
- Support for modern Python features such as the union operator (`|`) and `cached_property`.

## [0.3.0] - 2021-04-27

### Breaking Changes

- Full redesign of Secure API.
- Removal of cookie support.

### Added

- Added type hints for better developer experience.
- Added support for FastAPI.

### Changed

- Replaced Feature-Policy with Permissions-Policy (#10).

## [0.2.1] - 2018-12-24

### Added

- Added support for Masonite framework.
- Added docstrings for `SecureHeaders` and `SecureCookie`.

### Changed

- Upper-cased SameSite enum to `SameSite.LAX` / `SameSite.STRICT`.
- Modified hug implementation for SecureHeaders and SecureCookie.
- Renamed `Feature.Values.All` to `Feature.Values.All_` to avoid conflict with the built-in `all`.

### Fixed

- Removed trailing semicolon from Feature Policy.

## [0.2.0] - 2018-12-16

### Added

- Added policy builder `SecurePolicies` in `policies.py`.
- Added `Expires` header for legacy browser support.
- Added `max-age` directive to `Cache-Control` header.

### Changed

- Renamed `XXS` argument to `XXP`.
- Modified `set-cookie` to use Flask's native method.


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct

This Code of Conduct applies to all `secure` community spaces.

## Our Pledge

We’re committed to a welcoming, safe, equitable community for everyone. Treat others with respect and assume good faith.

## Expected Behavior

- Be kind, constructive, and professional.
- Respect different viewpoints and experiences.
- Take responsibility for your actions and help repair harm.
- Give and accept feedback gracefully.

## Unacceptable Behavior

- Harassment, threats, or hate/discrimination.
- Personal attacks, sexualized behavior, or stereotyping.
- Sharing someone’s private information without consent.
- Impersonation, misleading identity, or evasions of enforcement.
- Spam/promotional content outside community norms.

## Reporting

Report issues to **caleb@typeerror.com**. Maintainers will review reports promptly and handle them as confidentially as possible.

## Enforcement

Maintainers may take action appropriate to the situation, including warnings, temporary limits, suspension, or a permanent ban.

## Scope

Applies in project spaces (issues, PRs, discussions, chats) and when representing the project publicly.

## Attribution

Adapted from the Contributor Covenant v3.0: https://www.contributor-covenant.org/version/3/0/


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Thanks for helping make `secure` better. The following guidance keeps contributions aligned with the project’s release-quality standards.

## Development environment

1. Create a virtual environment and activate it:
   ```bash
   python -m venv .venv
   source .venv/bin/activate
   ```
2. Install the package in editable mode so local changes are picked up automatically:
   ```bash
   pip install -e .
   ```
3. Install the tooling used by the project:
   ```bash
   pip install pytest ruff
   ```
   _Optional:_ `uv` is the package manager used by the project for releases; you can use `uv run pytest` and `uv add ...` to manage dependencies, but it is not required for local development.

## Running tests, linting, and formatting

- **Run unit tests:** `pytest`
- **Run the linter:** `ruff check`
- **Apply formatting / fix issues:** `ruff format`

Run these commands before opening a pull request. If you rely on a different Python version, keep it within the supported range (Python 3.10+).

## Adding a header document

1. Add a new guide under `docs/headers/` named after the header (for example, `docs/headers/example_header.md`).
2. Mirror the structure of the existing header docs:
   - Start with a **Purpose** section that explains the header’s intent.
   - Describe the **Default behavior** and mention how the builder models that default.
   - Show a **Using with `Secure`** example and describe the builder API with method names.
   - Include **Resources** / **Attribution** and any security caveats.
3. Link the new document from `docs/README.md` (under the Security Headers list) so readers can discover it easily.
4. Ensure code snippets use the public API (`from secure import ...`), reference the appropriate response types, and avoid framework-specific terminology unless a callout is necessary.

## Adding a framework example

1. Update `docs/frameworks.md`:
   - Add the framework to the table of contents.
   - Include a short intro describing the framework’s model (WSGI vs ASGI, sync vs async).
   - Provide at least one working example showing how to wire `Secure` (middleware, hooks, or response-level helpers).
   - Mention the correct response type (`Response`, `JSONResponse`, etc.) or highlight that you are working with the framework’s default response object.
2. If the framework needs extra instructions (e.g., disabling Uvicorn’s `Server` header), document them in the same section.
3. Keep the tone focused on security headers rather than broader framework guidance.

## Commit conventions

- Keep commit messages short (<72 characters) and in the imperative (e.g., `docs: clarify defaults`).
- Prefix doc-only changes with `docs:` so reviewers immediately know the scope.
- Reference any related issue or PR in the description when applicable.
- Run linting/tests before committing to minimize follow-up work.

## Pull request checklist

- [ ] I have run `pytest` locally (or a representative suite) and addressed any failures.
- [ ] I have run `ruff check` and `ruff format` (when formatting attr).
- [ ] Documentation updates describe the new behavior (new header docs, framework guidance, etc.).
- [ ] If applicable, I have updated the release notes/CHANGELOG entry for new user-visible behavior.
- [ ] My changes follow the project’s security and contribution guidelines (this document).


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018-2024 Caleb Kinney

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.


================================================
FILE: README.md
================================================
# secure

[![PyPI Version](https://img.shields.io/pypi/v/secure.svg)](https://pypi.org/project/secure/) [![Python Versions](https://img.shields.io/pypi/pyversions/secure.svg)](https://pypi.org/project/secure/) [![License](https://img.shields.io/pypi/l/secure.svg)](https://github.com/TypeError/secure/blob/main/LICENSE)

Define HTTP security headers once. Apply them consistently across Python web apps.

`secure` provides a small, dependency-free API for configuring modern security headers across common Python web frameworks through ASGI middleware, WSGI middleware, or framework response hooks.

Use it when you want to avoid copy-pasted header strings spread across handlers, hooks, and middleware.

<p align="center">
  <img src="https://typeerror.com/assets/secure-hex.png" alt="secure hex" width="200">
</p>

Quick links: [Quick start](#quick-start) · [Headers](#what-headers-are-applied-by-default) · [Middleware](#middleware)

---

## Why use `secure`

Setting headers manually is fine for one endpoint. It gets harder to review when values are copied across routes, response hooks, reverse-proxy settings, and different framework integrations.

`secure` helps you:

- Keep one `Secure` policy object instead of scattered header strings
- Apply the same policy through ASGI middleware, WSGI middleware, or response hooks
- Start with practical presets, then customize headers for your application
- Use builders for complex headers such as Content Security Policy and Permissions Policy
- Make duplicate handling, header overwrites, and validation explicit

The defaults are a reasonable starting point, not a substitute for application-specific review. Content Security Policy in particular should be adjusted for the scripts, styles, assets, and third-party services your app actually uses.

---

## Installation

```bash
uv add secure
# or
pip install secure
```

---

## Quick start

### FastAPI / ASGI middleware

Use middleware when you can attach `secure` once and cover the whole application.

```python
from fastapi import FastAPI
from secure import Secure
from secure.middleware import SecureASGIMiddleware

app = FastAPI()
secure_headers = Secure.with_default_headers()

app.add_middleware(SecureASGIMiddleware, secure=secure_headers)
```

### Response hooks and handlers

Use the same policy object in framework hooks, middleware callbacks, or handlers that expose a response object with headers support.

```python
from secure import Secure

secure_headers = Secure.with_default_headers()

secure_headers.set_headers(response)
# or
await secure_headers.set_headers_async(response)
```

`Secure.with_default_headers()` maps to `Preset.BALANCED`, a practical default designed to be customized.

---

## What headers are applied by default

```http
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Content-Security-Policy: default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests
Strict-Transport-Security: max-age=31536000; includeSubDomains
Permissions-Policy: geolocation=(), microphone=(), camera=()
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
```

This preset reflects modern browser guidance from MDN and OWASP. It reduces cross-origin risk, prevents MIME sniffing, and provides a conservative Content Security Policy you can extend.

The default CSP avoids unsafe script execution by default, but CSP is context-sensitive. Interactive apps, dashboards, CDNs, analytics, embedded content, or other third-party integrations may require additional configuration.

---

## Presets

```python
from secure import Preset, Secure

Secure.from_preset(Preset.BALANCED)
Secure.from_preset(Preset.BASIC)
Secure.from_preset(Preset.STRICT)
```

- **BALANCED**: practical default for many applications
- **BASIC**: Helmet-style compatibility
- **STRICT**: tighter CSP and isolation

Start with BALANCED, review the generated headers, and move stricter only when needed.

---

## Middleware

`secure` provides both ASGI and WSGI middleware.

- Overwrites headers by default
- Allows controlled duplication via `multi_ok`
- Only modifies HTTP responses in ASGI apps

### ASGI

```python
from secure import Secure
from secure.middleware import SecureASGIMiddleware

secure_headers = Secure.with_default_headers()
app = SecureASGIMiddleware(app, secure=secure_headers)
```

### WSGI

```python
from secure import Secure
from secure.middleware import SecureWSGIMiddleware

secure_headers = Secure.with_default_headers()
app = SecureWSGIMiddleware(app, secure=secure_headers)
```

### Django example

```python
from secure import Secure

class SecureHeadersMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.secure = Secure.with_default_headers()

    def __call__(self, request):
        response = self.get_response(request)
        self.secure.set_headers(response)
        return response
```

---

## Framework integration examples

These examples show common integration paths. See the full [framework integration guide](https://github.com/TypeError/secure/tree/main/docs/frameworks.md) for additional frameworks and notes about first-class middleware versus minimal fallback examples.

### FastAPI

```python
from secure.middleware import SecureASGIMiddleware

app.add_middleware(SecureASGIMiddleware, secure=secure_headers)
```

### Flask

```python
@app.after_request
def add_security_headers(response):
    secure_headers.set_headers(response)
    return response
```

### Shiny for Python

```python
from shiny import App
from secure.middleware import SecureASGIMiddleware

app = SecureASGIMiddleware(App(), secure=secure_headers)
```

Interactive apps often need CSP configuration for their script, style, and asset loading patterns.

---

## Policy builders

### Content Security Policy

```python
from secure.headers import ContentSecurityPolicy

csp = (
    ContentSecurityPolicy()
    .default_src("'self'")
    .script_src("'self'", "cdn.example.com")
)
```

### Permissions Policy

```python
from secure.headers import PermissionsPolicy

permissions = (
    PermissionsPolicy()
    .geolocation("'self'")
    .camera("'none'")
)
```

---

## Header pipeline and validation

Use the optional pipeline when you need stricter control:

```python
secure_headers = (
    Secure.with_default_headers()
    .allowlist_headers(...)
    .deduplicate_headers(...)
    .validate_and_normalize_headers(...)
)
```

This makes header allowlists, deduplication, normalization, and validation explicit instead of leaving those choices spread across application code.

---

## Requirements

- Python 3.10+
- No external dependencies

---

## Documentation

Read the full [documentation](https://github.com/TypeError/secure/tree/main/docs).

---

## Learn more

- [MDN HTTP Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/)

---

## License

MIT License

---

## Contributing

Issues and pull requests are welcome.

---

## Acknowledgements

Built using guidance from MDN and OWASP secure headers recommendations.


================================================
FILE: docs/README.md
================================================
# Documentation

Use this index when you know what you need. For a first pass, start with the [top-level README](../README.md), then continue with [Usage](./usage.md).

The recommended default throughout the docs is `Secure.with_default_headers()`, which maps to `Preset.BALANCED`.

## Start here

- [Installation](./installation.md)
- [Usage](./usage.md)
- [Framework integration](./frameworks.md)
- [Migration notes](./migration.md)

## Reference

- [Configuration](./configuration.md)
- [Security considerations](./security_considerations.md)

## Header builders

- [Cache-Control](./headers/cache_control.md)
- [Content-Security-Policy](./headers/content_security_policy.md)
- [Cross-Origin-Embedder-Policy](./headers/cross_origin_embedder_policy.md)
- [Cross-Origin-Opener-Policy](./headers/cross_origin_opener_policy.md)
- [Cross-Origin-Resource-Policy](./headers/cross-origin-resource-policy.md)
- [Custom Header](./headers/custom_header.md)
- [Permissions-Policy](./headers/permissions_policy.md)
- [Referrer-Policy](./headers/referrer_policy.md)
- [Server](./headers/server.md)
- [Strict-Transport-Security](./headers/strict_transport_security.md)
- [X-Content-Type-Options](./headers/x_content_type_options.md)
- [X-DNS-Prefetch-Control](./headers/dns_prefetch_control.md)
- [X-Frame-Options](./headers/x_frame_options.md)
- [X-Permitted-Cross-Domain-Policies](./headers/x-permitted-cross-domain-policies.md)


================================================
FILE: docs/configuration.md
================================================
# Configuration Guide

This guide covers the parts of `secure` you are most likely to customize after the quick start. Keep `Secure` as the public entry point and pass it the builders you need.

## Default configuration

`Secure.with_default_headers()` uses `Preset.BALANCED`, which provides a modern baseline while keeping the header set lean:

- **Cross-Origin-Opener-Policy:** `same-origin`
- **Cross-Origin-Resource-Policy:** `same-origin`
- **Content-Security-Policy:** `default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests`
- **Strict-Transport-Security:** `max-age=31536000; includeSubDomains`
- **Permissions-Policy:** `geolocation=(), microphone=(), camera=()`
- **Referrer-Policy:** `strict-origin-when-cross-origin`
- **Server:** empty string
- **X-Content-Type-Options:** `nosniff`
- **X-Frame-Options:** `SAMEORIGIN`

Balanced intentionally skips `Cache-Control` and the compatibility headers (`X-Permitted-Cross-Domain-Policies`, `X-DNS-Prefetch-Control`, `Origin-Agent-Cluster`, `X-Download-Options`, `X-XSS-Protection`). Add them explicitly when your deployment still depends on them.

`Preset.BALANCED` is the recommended default. `Preset.BASIC` is the compatibility-oriented option that most closely matches Helmet-style defaults. `Preset.STRICT` is available when you want tighter CSP and stronger isolation. It is not the default.

The balanced CSP includes `'unsafe-inline'` in `style-src` for compatibility. It does not allow inline scripts by default. If your app needs a looser CSP, treat that as an app-specific adjustment and test it against real behavior.

## Customizing individual builders

All public header builders are re-exported from `secure`, so most applications can stay on the package-level API.

### `X-Frame-Options`

If you want to allow framing only from the same origin, use:

```python
from secure import Secure, XFrameOptions

secure_headers = Secure(xfo=XFrameOptions().sameorigin())
```

This protects against clickjacking while still allowing same-origin embedding.

### `Strict-Transport-Security`

To enforce HTTPS for all subdomains and opt into preload when you are ready, configure HSTS explicitly:

```python
from secure import Secure, StrictTransportSecurity

secure_headers = Secure(
    hsts=StrictTransportSecurity().max_age(63072000).include_subdomains().preload()
)
```

This enforces HTTPS for two years, applies the rule to subdomains, and opts into the preload list.

## Adding custom headers

Use `CustomHeader` for application-specific response headers that do not have a dedicated builder:

```python
from secure import CustomHeader, Secure

custom_header = CustomHeader("X-Custom-Header", "CustomValue")
secure_headers = Secure(custom=[custom_header])
```

## Starting from a preset

Every `Secure` instance exposes its configured builders through `headers_list`, so you can replace or extend a preset after construction:

```python
from secure import Preset, Secure, StrictTransportSecurity

secure_headers = Secure.from_preset(Preset.BALANCED)

secure_headers.headers_list = [
    header
    for header in secure_headers.headers_list
    if header.header_name != "Strict-Transport-Security"
]
secure_headers.headers_list.append(
    StrictTransportSecurity().max_age(63072000).include_subdomains()
)
```

This replaces the preset HSTS builder while leaving the rest of the preset untouched.
If you need the compatibility profile instead, start from `Preset.BASIC`.

## Middleware behavior

The ASGI and WSGI middleware classes use the same `Secure` instance you would use in hooks or handlers:

- Existing header values are overwritten by default for configured header names.
- Pass `multi_ok` when a header name should be preserved and appended instead.
- `SecureASGIMiddleware` only changes HTTP responses. WebSocket scopes pass through unchanged.

## Validation and normalization

If you need stronger guarantees before emission, `Secure` also exposes optional pipeline helpers:

- `allowlist_headers(...)` filters or rejects unexpected header names in the current `headers_list`.
- `deduplicate_headers(...)` resolves duplicate header names in `headers_list` before you build a single-valued mapping.
- `validate_and_normalize_headers(...)` validates and normalizes the current `header_items()`, then caches the single-valued mapping used by `.headers`, `set_headers()`, and `set_headers_async()`.

If you intentionally emit duplicate headers such as multiple `Content-Security-Policy` values, use `header_items()` instead of `.headers`.

For per-header builder details, see the docs under [headers](./headers).


================================================
FILE: docs/frameworks.md
================================================
# Framework Integration

`secure` keeps the same `Secure` object across frameworks. What changes is how you attach it.

Some sections below are first-class integrations with clear framework-level hooks or middleware. Others are intentionally minimal fallback examples where support is thinner or framework APIs vary by version.

## How to choose an integration style

- Use `set_headers()` when the response object is synchronous and you are already inside a response hook, middleware callback, or view.
- Use `set_headers_async()` in async middleware, hooks, or handlers when you want one helper that works safely across async response objects.
- Use `SecureWSGIMiddleware` when you want app-wide coverage and can wrap a WSGI application directly.
- Use `SecureASGIMiddleware` when you want app-wide coverage in an ASGI stack such as FastAPI, Starlette, or Shiny.

Prefer middleware when your framework makes it easy and you want app-wide coverage. Use per-response setters when you are integrating into an existing hook, view, or minimal handler path.

## Middleware behavior

`SecureASGIMiddleware` and `SecureWSGIMiddleware` share the same core behavior:

- Configured header names are overwritten by default so stale values do not accumulate.
- Pass `multi_ok` when a header should be preserved and `secure` should append its own value instead.
- The default `secure.MULTI_OK` setting allows controlled duplication for headers such as `Content-Security-Policy`.
- `SecureASGIMiddleware` only modifies HTTP responses. WebSocket and other non-HTTP scopes pass through unchanged.

Example:

```python
from secure import Secure
from secure.middleware import SecureASGIMiddleware

secure_headers = Secure.with_default_headers()
secured_app = SecureASGIMiddleware(app, secure=secure_headers, multi_ok={"content-security-policy"})
```

## Uvicorn `Server` header

Uvicorn adds `Server: uvicorn` by default. If you want `secure` to control the `Server` header, disable Uvicorn's default header with `--no-server-header` or `server_header=False`.

```python
import uvicorn

uvicorn.run(app, host="0.0.0.0", port=8000, server_header=False)
```

## Table of contents

- [aiohttp](#aiohttp)
- [Bottle](#bottle)
- [CherryPy](#cherrypy)
- [Dash](#dash)
- [Django](#django)
- [Falcon](#falcon)
- [FastAPI](#fastapi)
- [Flask](#flask)
- [Masonite](#masonite)
- [Morepath](#morepath)
- [Pyramid](#pyramid)
- [Quart](#quart)
- [Responder](#responder)
- [Sanic](#sanic)
- [Shiny](#shiny)
- [Starlette](#starlette)
- [Tornado](#tornado)
- [TurboGears](#turbogears)
- [Custom frameworks](#custom-frameworks)

## aiohttp

Async framework with first-class middleware support.

### Recommended: middleware with `set_headers_async()`

```python
from aiohttp import web
from secure import Secure

secure_headers = Secure.with_default_headers()


@web.middleware
async def add_security_headers(request, handler):
    response = await handler(request)
    await secure_headers.set_headers_async(response)
    return response


app = web.Application(middlewares=[add_security_headers])
```

### Alternative: set headers in a single handler

```python
from aiohttp import web
from secure import Secure

secure_headers = Secure.with_default_headers()


async def home(request):
    response = web.Response(text="Hello, world")
    await secure_headers.set_headers_async(response)
    return response
```

## Bottle

Small WSGI framework with request hooks.

### Recommended: `after_request` hook with `set_headers()`

```python
from bottle import Bottle, response
from secure import Secure

app = Bottle()
secure_headers = Secure.with_default_headers()


@app.hook("after_request")
def add_security_headers():
    secure_headers.set_headers(response)
```

### Fallback: set headers in a route

```python
from bottle import Bottle, response
from secure import Secure

app = Bottle()
secure_headers = Secure.with_default_headers()


@app.route("/")
def home():
    secure_headers.set_headers(response)
    return "Hello, world"
```

## CherryPy

Minimal fallback example. CherryPy exposes the response object in the handler, so handler-level mutation is the practical integration point.

### Minimal fallback: set headers in the exposed method

```python
import cherrypy
from secure import Secure

secure_headers = Secure.with_default_headers()


class App:
    @cherrypy.expose
    def index(self):
        secure_headers.set_headers(cherrypy.response)
        return b"Hello, world"


cherrypy.quickstart(App())
```

## Dash

Dash runs on top of Flask, so the usual Flask integration patterns apply.

### Recommended: Flask `after_request` on `app.server`

```python
import dash
from dash import html
from secure import Secure

app = dash.Dash(__name__)
server = app.server
secure_headers = Secure.with_default_headers()

app.layout = html.Div("Hello Dash!")


@server.after_request
def add_security_headers(response):
    secure_headers.set_headers(response)
    return response
```

### Alternative: `SecureWSGIMiddleware`

```python
import dash
from dash import html
from secure import Secure
from secure.middleware import SecureWSGIMiddleware

app = dash.Dash(__name__)
server = app.server
secure_headers = Secure.with_default_headers()

app.layout = html.Div("Hello Dash!")
server.wsgi_app = SecureWSGIMiddleware(server.wsgi_app, secure=secure_headers)
```

## Django

Django is usually best integrated through Django middleware rather than raw WSGI wrapping.

### Recommended: Django middleware class

Register the middleware class in your Django `MIDDLEWARE` setting.

```python
from secure import Secure


class SecureHeadersMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.secure = Secure.with_default_headers()

    def __call__(self, request):
        response = self.get_response(request)
        self.secure.set_headers(response)
        return response
```

### Fallback: set headers in a view

```python
from django.http import HttpResponse
from secure import Secure

secure_headers = Secure.with_default_headers()


def home(request):
    response = HttpResponse("Hello, world")
    secure_headers.set_headers(response)
    return response
```

## Falcon

Falcon exposes a clean response middleware hook.

### Recommended: Falcon middleware

```python
import falcon
from secure import Secure

secure_headers = Secure.with_default_headers()


class SecureMiddleware:
    def process_response(self, req, resp, resource, req_succeeded):
        secure_headers.set_headers(resp)


app = falcon.App(middleware=[SecureMiddleware()])
```

### Fallback: set headers in the resource

```python
import falcon
from secure import Secure

secure_headers = Secure.with_default_headers()


class HelloWorldResource:
    def on_get(self, req, resp):
        resp.text = "Hello, world"
        secure_headers.set_headers(resp)


app = falcon.App()
app.add_route("/", HelloWorldResource())
```

## FastAPI

ASGI framework. Middleware is the clearest default.

### Recommended: `SecureASGIMiddleware`

```python
from fastapi import FastAPI
from secure import Secure
from secure.middleware import SecureASGIMiddleware

app = FastAPI()
secure_headers = Secure.with_default_headers()

app.add_middleware(SecureASGIMiddleware, secure=secure_headers)
```

### Alternative: `@app.middleware("http")`

```python
from fastapi import FastAPI
from secure import Secure

app = FastAPI()
secure_headers = Secure.with_default_headers()


@app.middleware("http")
async def add_security_headers(request, call_next):
    response = await call_next(request)
    await secure_headers.set_headers_async(response)
    return response
```

### Fallback: set headers in one route

```python
from fastapi import FastAPI, Response
from secure import Secure

app = FastAPI()
secure_headers = Secure.with_default_headers()


@app.get("/")
def home(response: Response):
    secure_headers.set_headers(response)
    return {"hello": "world"}
```

## Flask

WSGI framework with a straightforward response hook.

### Recommended: `after_request`

```python
from flask import Flask
from secure import Secure

app = Flask(__name__)
secure_headers = Secure.with_default_headers()


@app.after_request
def add_security_headers(response):
    secure_headers.set_headers(response)
    return response
```

### Alternative: `SecureWSGIMiddleware`

```python
from flask import Flask
from secure import Secure
from secure.middleware import SecureWSGIMiddleware

app = Flask(__name__)
secure_headers = Secure.with_default_headers()

app.wsgi_app = SecureWSGIMiddleware(app.wsgi_app, secure=secure_headers)
```

## Masonite

Minimal fallback example. Masonite routing and response APIs vary by version, so apply `Secure` to the response object you actually return.

### Minimal fallback: apply to the response you return

```python
from secure import Secure

secure_headers = Secure.with_default_headers()


def home(response):
    rendered = response.json({"hello": "world"})
    secure_headers.set_headers(rendered)
    return rendered
```

## Morepath

Minimal fallback example. Morepath does not expose a conventional middleware layer for this, so view-level mutation is the practical integration point.

### Minimal fallback: set headers in the view

```python
import morepath
from secure import Secure

secure_headers = Secure.with_default_headers()


class App(morepath.App):
    pass


@App.path(path="")
class Root:
    pass


@App.view(model=Root)
def home(self, request):
    response = morepath.Response("Hello, world")
    secure_headers.set_headers(response)
    return response
```

## Pyramid

Pyramid applications commonly use tweens for cross-cutting response changes.

### Recommended: tween

Register the tween in your `Configurator` with `config.add_tween("yourpackage.security.add_security_headers")`.

```python
from secure import Secure

secure_headers = Secure.with_default_headers()


def add_security_headers(handler, registry):
    def tween(request):
        response = handler(request)
        secure_headers.set_headers(response)
        return response

    return tween
```

### Fallback: set headers in a view

```python
from pyramid.response import Response
from secure import Secure

secure_headers = Secure.with_default_headers()


def home(request):
    response = Response("Hello, world")
    secure_headers.set_headers(response)
    return response
```

## Quart

Async Flask-compatible framework.

### Recommended: `after_request` with `set_headers_async()`

```python
from quart import Quart
from secure import Secure

app = Quart(__name__)
secure_headers = Secure.with_default_headers()


@app.after_request
async def add_security_headers(response):
    await secure_headers.set_headers_async(response)
    return response
```

### Fallback: set headers in a route

```python
from quart import Quart, Response
from secure import Secure

app = Quart(__name__)
secure_headers = Secure.with_default_headers()


@app.route("/")
async def home():
    response = Response("Hello, world")
    await secure_headers.set_headers_async(response)
    return response
```

## Responder

Minimal fallback example. Route handlers typically own the response, so route-level mutation is the practical integration point.

### Minimal fallback: set headers in the route

```python
import responder
from secure import Secure

api = responder.API()
secure_headers = Secure.with_default_headers()


@api.route("/")
async def home(req, resp):
    resp.text = "Hello, world"
    await secure_headers.set_headers_async(resp)
```

## Sanic

Sanic exposes response middleware for app-wide coverage.

### Recommended: response middleware with `set_headers_async()`

```python
from sanic import Sanic
from secure import Secure

app = Sanic("secure-app")
secure_headers = Secure.with_default_headers()


@app.middleware("response")
async def add_security_headers(request, response):
    await secure_headers.set_headers_async(response)
    return response
```

Use route-level setters when you only need a small integration or do not want extra app wiring in tests.

### Fallback: set headers in a route

```python
from sanic import Sanic, response
from secure import Secure

app = Sanic("secure-app")
secure_headers = Secure.with_default_headers()


@app.get("/")
async def home(request):
    resp = response.text("Hello, world")
    await secure_headers.set_headers_async(resp)
    return resp
```

## Shiny

Shiny applications are ASGI apps, so ASGI middleware is the cleanest and most direct path.

### Recommended: `SecureASGIMiddleware`

```python
from secure import Secure
from secure.middleware import SecureASGIMiddleware
from shiny import App, ui

secure_headers = Secure.with_default_headers()

app_ui = ui.page_fluid("Hello Shiny!")


def server(input, output, session):
    pass


app = App(app_ui, server)
app = SecureASGIMiddleware(app, secure=secure_headers)
```

If your Shiny app loads external assets or opens additional browser connections, you may need to extend CSP directives such as `connect-src`, `script-src`, or `style-src`. Start from the balanced preset and test the running app before relaxing the policy.

## Starlette

ASGI framework. Use ASGI middleware unless you only need route-level control.

### Recommended: `SecureASGIMiddleware`

```python
from secure import Secure
from secure.middleware import SecureASGIMiddleware
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route

secure_headers = Secure.with_default_headers()


async def home(request):
    return PlainTextResponse("Hello, world")


app = Starlette(routes=[Route("/", home)])
app.add_middleware(SecureASGIMiddleware, secure=secure_headers)
```

### Alternative: set headers in an endpoint

```python
from secure import Secure
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Route

secure_headers = Secure.with_default_headers()


async def home(request):
    response = Response("Hello, world")
    await secure_headers.set_headers_async(response)
    return response


app = Starlette(routes=[Route("/", home)])
```

## Tornado

Minimal fallback example. Tornado usually applies headers inside request handlers, so handler-level mutation is the practical integration point.

### Minimal fallback: set headers in the handler

```python
import tornado.web
from secure import Secure

secure_headers = Secure.with_default_headers()


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
        secure_headers.set_headers(self)


app = tornado.web.Application([(r"/", MainHandler)])
```

## TurboGears

Minimal fallback example. If you do not already have a framework-level hook in place, controller-level mutation is the practical integration point.

### Minimal fallback: set headers in the controller

```python
from tg import Response, TGController, expose
from secure import Secure

secure_headers = Secure.with_default_headers()


class RootController(TGController):
    @expose()
    def index(self):
        response = Response("Hello, world")
        secure_headers.set_headers(response)
        return response


root = RootController()
```

## Custom frameworks

If your framework is not listed here, the integration rule is still simple: configure one `Secure` instance, then apply it to the response as late as possible before it is sent.

### Recommended: use the response object's setter or headers mapping

```python
from secure import Secure

secure_headers = Secure.with_default_headers()


def add_security_headers(response):
    secure_headers.set_headers(response)
    return response
```

### Fallback: emit header pairs manually

```python
from secure import Secure

secure_headers = Secure.with_default_headers()

for name, value in secure_headers.header_items():
    response.headers[name] = value
```


================================================
FILE: docs/headers/cache_control.md
================================================
# Cache-Control

## What it does

`Cache-Control` is a comma-separated list of **directives** that control caching behavior for both **requests** and **responses**. Used correctly, it helps prevent sensitive data from being cached and improves performance for cacheable assets.

## Minimal example

```python
from secure import CacheControl, Secure

secure_headers = Secure(
    cache=CacheControl().no_store().max_age(0)
)
```

## Resulting header

```http
Cache-Control: no-store, max-age=0
```

## Practical note

Use `no-store` for sensitive pages such as sign-in or account settings. `no-cache` is different. It allows storage but requires revalidation before reuse.

## Default behavior

If you create `CacheControl()` and do not add directives, it returns the library default value:

- **Default header value:** `no-store, max-age=0`

This is a secure baseline intended to prevent storage of sensitive responses.

## Using with `Secure`

If you do not configure any directives, the default value is emitted.
`Preset.STRICT` includes `Cache-Control: no-store, max-age=0`; `Preset.BASIC` and `Preset.BALANCED` leave caching unchanged unless you add this builder.

## Common recipes

### 1) Prevent storing (recommended for sensitive responses)

```python
from secure import CacheControl

cc = CacheControl()  # default: no-store, max-age=0
print(cc.header_name)   # Cache-Control
print(cc.header_value)  # no-store, max-age=0
```

### 2) Always revalidate (useful for dynamic HTML)

```python
cc = CacheControl().no_cache()
print(cc.header_value)  # no-cache
```

> Note: `no-cache` does **not** mean “do not store.” It means “store, but revalidate before reuse.”

### 3) Cache-busted static assets (long-lived)

If your assets are fingerprinted (e.g., `/app.4f3c1.js`), you can cache them aggressively:

```python
cc = CacheControl().public().max_age(31536000).immutable()
print(cc.header_value)  # public, max-age=31536000, immutable
```

### 4) Shared caches (CDNs/proxies) vs browser caches

```python
cc = CacheControl().s_maxage(604800).max_age(60)
print(cc.header_value)  # s-maxage=604800, max-age=60
```

`s-maxage` applies to shared caches and overrides `max-age` for them.

### 5) Stale content during revalidation / on error

```python
cc = (
    CacheControl()
    .max_age(604800)
    .stale_while_revalidate(86400)
    .stale_if_error(86400)
)
print(cc.header_value)  # max-age=604800, stale-while-revalidate=86400, stale-if-error=86400
```

## Builder API

### Boolean directives (no value)

- `.no_store()`, `.no_cache()`, `.no_transform()`
- `.public()`, `.private()`
- `.must_revalidate()`, `.proxy_revalidate()`
- `.immutable()`
- `.must_understand()` (recommended to pair with `.no_store()` for safe fallback)

### Parameterized directives (integer seconds)

- `.max_age(seconds)`
- `.s_maxage(seconds)`
- `.min_fresh(seconds)` (request)
- `.stale_while_revalidate(seconds)`
- `.stale_if_error(seconds)`

### Request directives

- `.only_if_cached()`
- `.max_stale(seconds=None)` (if omitted, accepts staleness of any age)

## Escape hatches

### `.value("...")`

Set an explicit header value (replaces all configured directives):

```python
cc = CacheControl().value("no-store, max-age=0")
print(cc.header_value)  # no-store, max-age=0
```

### `.custom("token")`

Add a **non-standard / non-MDN** directive token (for niche proxies/CDNs):

```python
cc = CacheControl().custom("x-cache-mode=aggressive")
print(cc.header_value)  # x-cache-mode=aggressive
```

### `.clear()`

Reset to the default (no directives configured; default value will be returned).

## Deterministic output & overwrites

- Directives are rendered as a **stable**, comma-separated list.
- Repeating a parameterized directive overwrites the previous value (e.g., calling `.max_age(60)` then `.max_age(0)` results in `max-age=0`).
- The builder rejects obvious header-splitting primitives (CR/LF) in `.value(...)` and `.custom(...)`.

## Attribution

This library implements security recommendations and behavior described by:

- [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/#cache-control) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/headers/content_security_policy.md
================================================
# Content-Security-Policy (CSP)

## What it does

The `Content-Security-Policy` (CSP) response header helps mitigate cross-site scripting (XSS), data injection, and related attacks by restricting where content can be loaded from (scripts, styles, images, fonts, connections, frames, etc.).

CSP is expressed as a list of **directives** separated by semicolons:

```http
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'
```

## Minimal example

```python
from secure import ContentSecurityPolicy, Secure

csp = (
    ContentSecurityPolicy()
    .default_src("'self'")
    .object_src("'none'")
    .base_uri("'self'")
)

secure_headers = Secure(csp=csp)
```

## Resulting header

```http
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'
```

## Practical note

Do not treat `'unsafe-inline'` as a default starting point for scripts. Use it only as an app-specific compatibility adjustment and test the real app before and after any CSP change.

## Library defaults

If you create a `ContentSecurityPolicy()` and do not configure any directives, it returns the library default:

```text
default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'
```

This matches the builder default.
The built-in presets use explicit CSP builders rather than this bare builder default. `Preset.BASIC` and `Preset.BALANCED` add `font-src`, `img-src`, `script-src-attr`, `style-src`, and `upgrade-insecure-requests`.

## Best-practice baseline

A common “safe baseline” CSP includes:

- `default-src 'self'`
- `object-src 'none'`
- `base-uri 'self'`
- `frame-ancestors 'self'` (or `'none'` if you never want to be framed)
- `form-action 'self'`
- optionally `upgrade-insecure-requests`

> CSP is powerful but can break applications if rolled out too aggressively. Start with a report-only policy, review violations, then enforce.

---

## Configuration with `Secure`

Once you configure `csp`, apply it in your framework integration:

```python
# Flask example
from flask import Flask, Response

app = Flask(__name__)
secure_headers = Secure(csp=csp)

@app.after_request
def add_security_headers(response: Response) -> Response:
    secure_headers.set_headers(response)
    return response
```

### Report-only mode

To observe violations without enforcing (recommended for rollout):

```python
csp_report_only = (
    ContentSecurityPolicy()
    .report_only()
    .default_src("'self'")
    .script_src("'self'")
)

secure_headers = Secure(csp=csp_report_only)
```

Use `.enforce()` to switch back to the enforcing header name.

---

## Fluent directive methods

Common methods include:

- Fetch directives: `default_src`, `script_src`, `style_src`, `img_src`, `font_src`, `connect_src`, `media_src`, `frame_src`, `worker_src`, `manifest_src`, `fenced_frame_src`, `object_src`
- Navigation / embedding: `base_uri`, `form_action`, `frame_ancestors`
- Policy controls: `sandbox`, `upgrade_insecure_requests`
- Reporting: `report_to`, `report_uri` _(deprecated in MDN)_

### Deterministic output and deduplication

- Each directive name appears at most once.
- Tokens passed to a directive are deduplicated (first-seen order preserved).
- Serialization is deterministic and uses `"; "` between directives.

---

## Helper utilities

### Keywords

Use `keyword()` to safely produce quoted CSP keywords like `'self'` and `'none'`:

```python
ContentSecurityPolicy.keyword("self")  # "'self'"
ContentSecurityPolicy.keyword("none")  # "'none'"
```

### Nonces

Use `nonce()` to produce a CSP nonce source expression:

```python
nonce_value = "abc123=="  # base64 / url-safe base64
ContentSecurityPolicy.nonce(nonce_value)  # "'nonce-abc123=='"
```

---

## Escape hatches

### Set an exact policy string

If you need full control (or want to carry over an existing CSP string), use `.value(...)`:

```python
csp = ContentSecurityPolicy().value(
    "default-src 'self'; script-src 'self' https://cdn.example; object-src 'none'"
)
```

`.set(...)` is an alias for `.value(...)`.

### Clear configuration

```python
csp = ContentSecurityPolicy().default_src(ContentSecurityPolicy.keyword("self"))
csp.clear()  # resets back to library default behavior
```

### Custom directives

If you need a directive not covered by a helper method:

```python
csp = (
    ContentSecurityPolicy()
    .custom_directive("default-src", "'self'")
    .custom_directive("script-src", "'self'")
)

# `.custom(...)` is an alias
```

---

## Nonce + `strict-dynamic` example (recommended pattern)

When you use nonces, the nonce must be generated **per response** and also placed in your HTML script tag(s).
A common pattern is to generate the nonce in request context, then build CSP using it.

### Framework-agnostic CSP construction

```python
import secrets
from secure import ContentSecurityPolicy

nonce = secrets.token_urlsafe(16)

csp = (
    ContentSecurityPolicy()
    .default_src("'self'")
    .script_src(
        ContentSecurityPolicy.nonce(nonce),
        ContentSecurityPolicy.keyword("strict-dynamic"),
    )
    .object_src("'none'")
)

print(csp.header_value)
# default-src 'self'; script-src 'nonce-...' 'strict-dynamic'; object-src 'none'
```

### Flask pattern (nonce shared via `g`)

```python
import secrets
from flask import Flask, Response, g

from secure import ContentSecurityPolicy, Secure

app = Flask(__name__)

@app.before_request
def set_nonce() -> None:
    g.csp_nonce = secrets.token_urlsafe(16)

@app.after_request
def add_security_headers(response: Response) -> Response:
    csp = (
        ContentSecurityPolicy()
        .default_src("'self'")
        .script_src(
            ContentSecurityPolicy.nonce(g.csp_nonce),
            ContentSecurityPolicy.keyword("strict-dynamic"),
        )
        .style_src("'self'")
        .object_src("'none'")
    )
    Secure(csp=csp).set_headers(response)
    return response
```

In your HTML rendering, use the same nonce:

```html
<script nonce="{{ g.csp_nonce }}">
  console.log("Allowed because nonce matches CSP");
</script>
```

---

## Reporting notes (MDN)

- `report-to` is the modern mechanism.
- `report-uri` is deprecated in MDN; some browsers that support `report-to` may ignore `report-uri`.
- If you need broad compatibility during migration, you may specify both.

---

## References

- [MDN: Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy)
- [MDN: CSP guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP)
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/#content-security-policy)

## Attribution

This library implements security recommendations and reference material from:

- MDN Web Docs (licensed under CC-BY-SA 2.5)
- OWASP Secure Headers Project (licensed under CC-BY-SA 4.0)


================================================
FILE: docs/headers/cross-origin-resource-policy.md
================================================
# Cross-Origin-Resource-Policy (CORP)

## What it does

The `Cross-Origin-Resource-Policy` (CORP) response header lets a **resource owner** declare what sites/origins are allowed to load that resource.

This header is commonly used to reduce cross-origin data leaks by controlling who can load your resources (images, scripts, etc.) and by blocking certain cross-origin/cross-site `no-cors` requests when the policy is more restrictive.

## Minimal example

```python
from secure import CrossOriginResourcePolicy, Secure

secure_headers = Secure(
    corp=CrossOriginResourcePolicy().same_origin()
)
```

## Resulting header

```http
Cross-Origin-Resource-Policy: same-origin
```

## Practical note

`same-origin` is a good default for sensitive resources. If you serve shared assets across subdomains, test `same-site` carefully before widening the policy further.

## Best Practices

- **`same-origin`**: Strong default for sensitive resources; only allow loads from the same origin.
- **`same-site`**: Useful when you need to share resources across subdomains on the same “site” but not with unrelated sites.
- **`cross-origin`**: Most permissive; allow any origin to load the resource (use intentionally, not by accident).

## Configuration with `Secure`

The `CrossOriginResourcePolicy` class provides a fluent API for setting CORP directives and integrates cleanly with `Secure(...)`.

> Library default: if you do not change it, the library’s default value is `same-origin`.
> Presets: `Preset.BASIC` and `Preset.BALANCED` include `same-origin`; `Preset.STRICT` does not add CORP by default.

### Methods Available

- **`same_origin()`**: Set `Cross-Origin-Resource-Policy: same-origin`
- **`same_site()`**: Set `Cross-Origin-Resource-Policy: same-site`
- **`cross_origin()`**: Set `Cross-Origin-Resource-Policy: cross-origin`
- **`value(value)`**: Set an explicit value (escape hatch; canonicalizes known directives)
- **`clear()`**: Reset to the library default value
- **`set(value)`**: Backwards-compatible alias for `value(...)`

## Example Usage

To restrict resource loading to the same origin:

```python
corp = CrossOriginResourcePolicy().same_origin()
print(corp.header_name)   # Output: 'Cross-Origin-Resource-Policy'
print(corp.header_value)  # Output: 'same-origin'
```

To allow resource loading from the same site (useful for subdomains):

```python
corp = CrossOriginResourcePolicy().same_site()
print(corp.header_value)  # Output: 'same-site'
```

## **Attribution**

This library implements security recommendations from trusted sources:

- [MDN Web Docs: `Cross-Origin-Resource-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Resource-Policy) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project: Cross-Origin-Resource-Policy](https://owasp.org/www-project-secure-headers/#cross-origin-resource-policy) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/headers/cross_origin_embedder_policy.md
================================================
# Cross-Origin-Embedder-Policy (COEP)

## What it does

The **`Cross-Origin-Embedder-Policy`** response header configures the current document’s policy for **loading and embedding cross-origin resources**.

At a high level, COEP lets you:

- keep the default behavior (`unsafe-none`),
- require explicit opt-in via **CORP** (`Cross-Origin-Resource-Policy`) and/or **CORS** (`require-corp`), or
- allow some cross-origin loading while **stripping credentials** (`credentialless`).

## Minimal example

```python
from secure import CrossOriginEmbedderPolicy, CrossOriginOpenerPolicy, Secure

secure_headers = Secure(
    coep=CrossOriginEmbedderPolicy().require_corp(),
    coop=CrossOriginOpenerPolicy().same_origin(),
)
```

## Resulting header

```http
Cross-Origin-Embedder-Policy: require-corp
```

## Practical note

COEP is most useful when you are intentionally working toward cross-origin isolation. It can break third-party assets that do not send compatible CORP or CORS headers, so test the full app before enabling it broadly.

## Directive values

COEP is a **single-value** header (choose one):

| Value            | Meaning                                                                                                                                                                                                     |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unsafe-none`    | Allows cross-origin resources **without** explicit permission via CORP or CORS. _(This is the browser default if the header is not sent.)_                                                                  |
| `require-corp`   | Blocks cross-origin resource loading unless the resource is permitted via **CORP** (for `no-cors` requests) or via **CORS** (for `cors` requests).                                                          |
| `credentialless` | Allows `no-cors` cross-origin resource loading **without** CORP opt-in, but sends requests **without credentials** (cookies omitted and ignored). For other request modes, behavior matches `require-corp`. |

## Library default vs browser default

- **Browser behavior when the header is absent:** `unsafe-none`.
- **This library’s builder default:** `require-corp` (a stricter, security-forward default).

If you want “no-op” behavior, you must explicitly choose it:

```python
from secure import CrossOriginEmbedderPolicy

coep = CrossOriginEmbedderPolicy().unsafe_none()
```

## Cross-origin isolation (COOP + COEP)

Some powerful browser features require your document to be **cross-origin isolated**. To enable this, you generally need:

- `Cross-Origin-Embedder-Policy: require-corp` **or** `credentialless`, and
- `Cross-Origin-Opener-Policy: same-origin`.

## Usage with `Secure`

You can inspect the emitted header pairs with `secure_headers.header_items()` if you need to confirm the final output.

`Preset.STRICT` includes COEP by default; `Preset.BASIC` and `Preset.BALANCED` do not.

## Header builder API

```python
from secure import CrossOriginEmbedderPolicy

coep = (
    CrossOriginEmbedderPolicy()
    .credentialless()   # or .require_corp() / .unsafe_none()
)

print(coep.header_name)   # "Cross-Origin-Embedder-Policy"
print(coep.header_value)  # "credentialless"
```

### Methods

- `unsafe_none()`: set the value to `unsafe-none`
- `require_corp()`: set the value to `require-corp`
- `credentialless()`: set the value to `credentialless`
- `set(value)`: set a custom value
- `clear()`: reset to the library default (`require-corp`)

## Notes / gotchas

- `require-corp` can break embedding third-party resources unless they opt-in via CORP or are requested in `cors` mode.
- `credentialless` can be a pragmatic alternative for some `no-cors` resources, but it comes with the tradeoff of **no cookies/credentials**.

## Attribution

This library implements security recommendations and definitions from trusted sources:

- [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy) (CC-BY-SA 2.5)
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/#cross-origin-embedder-policy) (CC-BY-SA 4.0)


================================================
FILE: docs/headers/cross_origin_opener_policy.md
================================================
# Cross-Origin-Opener-Policy

## What it does

The `Cross-Origin-Opener-Policy` (COOP) response header controls whether documents opened via `Window.open()` (or navigations) share the same **browsing context group (BCG)** as their opener. When a document is opened into a new BCG, references between the opener and the opened document are severed, which helps mitigate cross-origin attacks often referred to as **XS-Leaks**.

## Minimal example

```python
from secure import CrossOriginOpenerPolicy, Secure

secure_headers = Secure(
    coop=CrossOriginOpenerPolicy().same_origin()
)
```

## Resulting header

```http
Cross-Origin-Opener-Policy: same-origin
```

## Practical note

`same-origin` is a strong default, but popup-based flows such as OAuth or payment providers sometimes need `same-origin-allow-popups`. Test those flows before tightening COOP.

## Defaults

- **Browser/spec behavior:** If the header is **absent**, the effective behavior is equivalent to `unsafe-none` (opt-out).
- **Library default:** This library’s builder defaults to `same-origin` (a secure default), and the built-in presets also configure COOP as `same-origin`.

## Best Practices

- **`same-origin`**: Strong isolation; commonly used for cross-origin isolation (often paired with COEP).
- **`same-origin-allow-popups`**: Like `same-origin`, but relaxes behavior for integrations that open trusted popups/tabs that opt out (e.g., OAuth/payment flows).
- **`noopener-allow-popups`**: Always isolates into a new BCG (except when opened by a same-origin document that also uses `noopener-allow-popups`). Useful when you need to isolate **same-origin** apps from each other (e.g., `/chat` vs `/passwords`) while still allowing popups.
- **`unsafe-none`**: Opts out of COOP isolation.

## Configuration with `Secure`

Use the `CrossOriginOpenerPolicy` builder and pass it into `Secure(...)`.

## Methods Available

Directive helpers (recommended):

- `same_origin()`
- `same_origin_allow_popups()`
- `noopener_allow_popups()`
- `unsafe_none()`

Escape hatches:

- `value("...")` / `custom("...")`: Set a raw value (rejects CR/LF).
- `set("...")`: Backwards-compatible alias for `value(...)`.
- `clear()`: Reset back to the library default (`same-origin`).

## Example Usage

```python
from secure import CrossOriginOpenerPolicy, Secure

coop = CrossOriginOpenerPolicy().same_origin()
print(coop.header_name)   # 'Cross-Origin-Opener-Policy'
print(coop.header_value)  # 'same-origin'

secure_headers = Secure(coop=coop)
```

## Notes

- For **cross-origin isolation** (e.g., `SharedArrayBuffer`), COOP is typically paired with **COEP** (often `require-corp`), and your app must satisfy other isolation requirements.

## Attribution

This library implements security recommendations from trusted sources:

- [MDN Web Docs: Cross-Origin-Opener-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/#cross-origin-opener-policy) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/headers/custom_header.md
================================================
# CustomHeader Class

## What it does

The `CustomHeader` class lets you create arbitrary HTTP response headers when `secure` does not provide a dedicated builder.

## Minimal example

```python
from secure import CustomHeader, Secure

custom_header = CustomHeader("X-Custom-Header", "CustomValue")
secure_headers = Secure(custom=[custom_header])
```

## Resulting header

```http
X-Custom-Header: CustomValue
```

## Practical note

Use `CustomHeader` for app-specific or infrastructure-specific headers. If you later call `allowlist_headers(...)`, remember to allow the custom name explicitly when needed.

## Best Practices

- Prefer standard header names when they exist; use custom names only for application- or infrastructure-specific behavior.
- If you use `allowlist_headers(...)`, remember that custom names may need to be added through `allow_extra=...`.

## Configuration with `Secure`

Use `CustomHeader` when you need a header without a dedicated builder. You can set the name and value directly, then update the value later if needed.

### Methods Available

- **`set(value)` / `value(value)`**: Updates the value of the custom header.
- **`header_value`**: Property that retrieves the current value of the custom header.

## Example Usage

To define a custom header and use it in a secure configuration:

```python
from secure import CustomHeader

custom_header = CustomHeader("X-Custom-Header", "CustomValue")
print(custom_header.header_name)   # Output: 'X-Custom-Header'
print(custom_header.header_value)  # Output: 'CustomValue'

# Update the value
custom_header.set("NewValue")
print(custom_header.header_value)  # Output: 'NewValue'
```

## **Resources**

- [MDN Web Docs: HTTP Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)

## **Attribution**

This library implements security recommendations from trusted sources:

- [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/headers/dns_prefetch_control.md
================================================
# X-DNS-Prefetch-Control

## What it does

`X-DNS-Prefetch-Control` controls **DNS prefetching**, where browsers may proactively resolve domain names for links and referenced subresources (images, CSS, JS, etc.) in the background to reduce perceived latency.

## Minimal example

```python
from secure import Secure, XDnsPrefetchControl

secure_headers = Secure(
    xdfc=XDnsPrefetchControl().off()
)
```

## Resulting header

```http
X-DNS-Prefetch-Control: off
```

## Practical note

When this header is absent, supporting browsers commonly behave as if DNS prefetching is on. Add the header only when you want to state a clear preference.

## Default behavior

If you create `XDnsPrefetchControl()` and do not set a directive, it returns the library default value:

- **Default header value:** `off`

> Note (MDN behavior): In browsers that support DNS prefetching, if this header is **not present**, the effective behavior is typically **`on`**. This library’s default is **privacy-first** when you choose to emit the header.

## Using with `Secure`

If you don’t configure anything, the default value is emitted.
`Preset.BASIC` includes `X-DNS-Prefetch-Control: off`; `Preset.BALANCED` and `Preset.STRICT` leave it out unless you add it explicitly.

## Common recipes

### 1) Disable DNS prefetching (recommended when you don’t control outbound links)

```python
from secure import XDnsPrefetchControl

xdfc = XDnsPrefetchControl()  # default: off
print(xdfc.header_name)   # X-DNS-Prefetch-Control
print(xdfc.header_value)  # off
```

### 2) Enable DNS prefetching

```python
xdfc = XDnsPrefetchControl().on()
print(xdfc.header_value)  # on
```

### 3) Backwards-compatible builder names

If you prefer the older API vocabulary:

```python
xdfc = XDnsPrefetchControl().allow()   # == .on()
xdfc = XDnsPrefetchControl().disable() # == .off()
```

## Builder API

### Canonical directives

- `.on()`
  Enables DNS prefetching (commonly the effective behavior when the header is absent in supporting browsers).

- `.off()`
  Disables DNS prefetching (useful to reduce information leakage to third-party domains).

### Backwards-compatible aliases

- `.allow()` → same as `.on()`
- `.disable()` → same as `.off()`

## Escape hatches

### `.value("...")` / `.set("...")`

Set an explicit header value (replaces the current value):

```python
xdfc = XDnsPrefetchControl().value("off")
print(xdfc.header_value)  # off
```

If you pass `ON` / `Off` (any casing), the builder normalizes to `on` / `off` for stable output.

### `.custom("token")`

Set a **non-standard / non-MDN** token (escape hatch):

```python
xdfc = XDnsPrefetchControl().custom("off")
print(xdfc.header_value)  # off
```

(For this header, non-`on`/`off` values are unusual, but the escape hatch exists for consistency across the library.)

### `.clear()`

Reset to the library default:

```python
xdfc = XDnsPrefetchControl().on().clear()
print(xdfc.header_value)  # off
```

## Deterministic output & overwrites

- Output is always a **single token** (`on` or `off`) when using `.on()` / `.off()` (stable and deterministic).
- Setting the value multiple times overwrites the previous value (last call wins).
- `.set(...)`, `.value(...)`, and `.custom(...)` reject CR/LF; `Secure.validate_and_normalize_headers(...)` performs the broader normalization pass.

## Compatibility notes

- This header is **non-standard**.
- Browser behavior differs across engines and versions; treat this as a best-effort control rather than a guaranteed security boundary.

## Attribution

This library implements security recommendations and behavior described by:

- [MDN Web Docs: X-DNS-Prefetch-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-DNS-Prefetch-Control) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project: X-DNS-Prefetch-Control](https://owasp.org/www-project-secure-headers/#x-dns-prefetch-control) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/headers/permissions_policy.md
================================================
# Permissions-Policy

## What it does

The `Permissions-Policy` HTTP response header lets you enable or disable access to selected browser features and powerful APIs in the current document and in nested browsing contexts (iframes). It replaces the deprecated `Feature-Policy` header.

In this library, `PermissionsPolicy` is a fluent builder for producing a single `Permissions-Policy` header value, suitable for applying via `Secure`.

## Minimal example

```python
from secure import PermissionsPolicy, Secure

secure_headers = Secure(
    permissions=PermissionsPolicy()
        .geolocation()
        .microphone()
        .camera()
)
```

## Resulting header

```http
Permissions-Policy: geolocation=(), microphone=(), camera=()
```

## Practical note

Browser support varies by feature. Keep the policy restrictive, then test the specific features your app actually needs in real browsers.

## Best practices

- Start restrictive: disable features you don’t need to reduce attack surface and protect privacy.
- Enable selectively: allow features only where required, and only for trusted origins.
- Validate in real browsers: support varies by feature and browser; test the behaviors you rely on.

## Configuration with `Secure`

`Preset.BALANCED` and `Preset.STRICT` include `geolocation=(), microphone=(), camera=()` by default; `Preset.BASIC` does not add `Permissions-Policy`.

## Allowlist syntax

`PermissionsPolicy` uses MDN-style allowlist syntax for each directive:

- **No tokens** → `()` (feature disabled)
- **`"*"`** → `*` (feature allowed everywhere; must be used alone)
- **`"self"` / `"src"`** → tokens for same-origin / iframe source origin
- **Origins** → pass a URL (e.g. `"https://a.example.com"`); it is emitted as a double-quoted origin in the header value

Examples:

```python
policy = (
    PermissionsPolicy()
    .geolocation("*")  # geolocation=*
    .camera("self", "https://a.example.com")  # camera=(self "https://a.example.com")
    .microphone()  # microphone=()
)
print(policy.header_value)
```

## Methods

Common methods you’ll use:

- **`geolocation(*allowlist)`**, **`camera(*allowlist)`**, **`microphone(*allowlist)`**, etc.: configure specific directives.
- **`add_directive(directive, *allowlist)`** (alias: **`directive(...)`**): set any directive by name (future-proof when browsers add new ones).
- **`value(raw)`** (alias: **`set(raw)`**): set a complete prebuilt header value (escape hatch; bypasses directive building).
- **`clear()`**: remove all configured directives and any raw override.

## Example usage

```python
from secure import PermissionsPolicy, Secure

permissions_policy = (
    PermissionsPolicy()
    .geolocation()  # disabled
    .camera("self", "https://a.example.com")
    .microphone("self")
)

print(permissions_policy.header_name)   # 'Permissions-Policy'
print(permissions_policy.header_value)  # 'geolocation=(), camera=(self "https://a.example.com"), microphone=(self)'

secure_headers = Secure(permissions=permissions_policy)
```

## Resources

- [MDN Web Docs: Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy)
- [OWASP Secure Headers Project: Permissions-Policy](https://owasp.org/www-project-secure-headers/#permissions-policy)

## Attribution

This library implements security recommendations from trusted sources:

- MDN Web Docs (licensed under CC-BY-SA 2.5)
- OWASP Secure Headers Project (licensed under CC-BY-SA 4.0)


================================================
FILE: docs/headers/referrer_policy.md
================================================
# Referrer-Policy

## What it does

The `Referrer-Policy` response header controls how much referrer information (sent via the `Referer` header) is included with outgoing requests. It is primarily a privacy and data-minimization control, with important security implications when navigating across origins or downgrading from HTTPS to HTTP.

> Note: `Referer` is intentionally misspelled in HTTP. `Referrer-Policy` does **not** share that misspelling.

## Minimal example

```python
from secure import ReferrerPolicy, Secure

secure_headers = Secure(
    referrer=ReferrerPolicy().strict_origin_when_cross_origin()
)
```

## Resulting header

```http
Referrer-Policy: strict-origin-when-cross-origin
```

## Practical note

`strict-origin-when-cross-origin` is the recommended default because it preserves same-origin behavior while avoiding full URL leakage across origins. Move to `no-referrer` only when you want the strictest privacy posture.

## Default behavior

**Default header value:** `strict-origin-when-cross-origin`

This matches modern browser defaults: if no policy is specified (or the provided value is invalid), the effective policy is `strict-origin-when-cross-origin`.

## Best practices (recommended choices)

- **`strict-origin-when-cross-origin` (recommended default)**  
  Sends the full referrer (origin + path + query) for same-origin requests; sends **origin only** for cross-origin HTTPS→HTTPS; sends **no `Referer`** when downgrading (HTTPS→HTTP).
- **`no-referrer` (max privacy)**  
  Omits the `Referer` header entirely for all requests.
- **`same-origin` (strict privacy across sites)**  
  Sends referrer only for same-origin requests; omits it for cross-origin requests.
- **Avoid `unsafe-url`** unless you fully understand the impact (it can leak sensitive URL data across origins and to insecure destinations).

## Configuration with `Secure`

```python
from secure import ReferrerPolicy, Secure

secure_headers = Secure(
    referrer=ReferrerPolicy()  # uses the default: strict-origin-when-cross-origin
)
```

`Preset.BALANCED` uses `strict-origin-when-cross-origin`; `Preset.BASIC` and `Preset.STRICT` use `no-referrer`.

### Set a single explicit policy

Use `value(...)` (or `custom(...)`) when you want to **replace** any configured policies and set exactly one value:

```python
from secure import ReferrerPolicy, Secure

secure_headers = Secure(
    referrer=ReferrerPolicy().value("no-referrer")
)
```

You can also use the fluent directive helpers:

```python
secure_headers = Secure(
    referrer=ReferrerPolicy().no_referrer()
)
```

### Specify a fallback policy list (HTTP header only)

Browsers support a **comma-separated list** in the `Referrer-Policy` HTTP header. The desired (most modern) policy should be listed **last**.

```python
from secure import ReferrerPolicy

rp = ReferrerPolicy().fallback("no-referrer", "strict-origin-when-cross-origin")
print(rp.header_name)   # Referrer-Policy
print(rp.header_value)  # no-referrer, strict-origin-when-cross-origin
```

You can build the same list with `.add(...)`:

```python
rp = (
    ReferrerPolicy()
    .clear()
    .add("no-referrer")
    .add("strict-origin-when-cross-origin")
)
```

> Note: the fallback _list_ behavior is supported in the HTTP header, but not in the HTML `referrerpolicy` attribute.

## API reference (ReferrerPolicy)

### Core builder methods

- `value("...")` / `custom("...")`
  Replace all configured policies with the provided value (supports comma-separated lists).
- `add("...")` / `set("...")`
  Append one or more policy tokens (supports comma-separated lists). Duplicate tokens are ignored.
- `fallback(*policies)`
  Replace the current policies with an explicit ordered fallback list.
- `clear()`
  Clear configured policies (returns to default behavior unless you add values afterward).

### Directive helpers (MDN policies)

Each of these appends the corresponding token (same behavior as `add("token")`):

- `no_referrer()` → `no-referrer`
  Omits the `Referer` header entirely.
- `no_referrer_when_downgrade()` → `no-referrer-when-downgrade`
  Sends full referrer for same-or-more secure requests; omits referrer on downgrade (HTTPS→HTTP).
- `origin()` → `origin`
  Sends only the origin (scheme + host + port).
- `origin_when_cross_origin()` → `origin-when-cross-origin`
  Same-origin: full referrer; cross-origin and downgrade: origin only.
- `same_origin()` → `same-origin`
  Same-origin: full referrer; cross-origin: omit referrer.
- `strict_origin()` → `strict-origin`
  Sends only origin for same-security requests; omits on downgrade (HTTPS→HTTP).
- `strict_origin_when_cross_origin()` → `strict-origin-when-cross-origin`
  Same-origin: full referrer; cross-origin HTTPS→HTTPS: origin only; downgrade: omit.
- `unsafe_url()` → `unsafe-url`
  Sends origin + path + query for all requests (generally discouraged; may leak sensitive data).

## Example usage

```python
from secure import ReferrerPolicy

referrer_policy = ReferrerPolicy().strict_origin_when_cross_origin()
print(referrer_policy.header_name)   # 'Referrer-Policy'
print(referrer_policy.header_value)  # 'strict-origin-when-cross-origin'
```

## Resources

- MDN Web Docs: Referrer-Policy
  [https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy)
- OWASP Secure Headers Project: Referrer-Policy
  [https://owasp.org/www-project-secure-headers/#referrer-policy](https://owasp.org/www-project-secure-headers/#referrer-policy)

## Attribution

This library implements security recommendations from trusted sources:

- MDN Web Docs (licensed under CC-BY-SA 2.5)
  [https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor](https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor)
  [https://creativecommons.org/licenses/by-sa/2.5/](https://creativecommons.org/licenses/by-sa/2.5/)
- OWASP Secure Headers Project (licensed under CC-BY-SA 4.0)
  [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)


================================================
FILE: docs/headers/server.md
================================================
# Server Header

## What it does

The `Server` header can reveal details about the software handling the request. In `secure`, the builder defaults to an empty string so your application can avoid adding identifying detail when the surrounding stack allows it.

## Minimal example

```python
from secure import Secure, Server

secure_headers = Secure(
    server=Server().set("")
)
```

## Resulting header

```http
Server:
```

## Practical note

Application code can only control this header if the surrounding stack does not re-add its own value. Check your ASGI server, WSGI server, proxy, or CDN settings too.

## Best Practices

- **Set an empty value or custom string**: Use an empty or generic value when you want `secure` to control the header.
- **Avoid exposing server information**: Avoid leaving the default server response, which may expose sensitive version information.
- **Check upstream defaults**: Proxies, ASGI servers, and framework middleware may still add their own `Server` header unless you disable that behavior.

## Configuration with `Secure`

Use `Server` to control the `Server` header value. Its default value is an empty string.

### Methods Available

- **`set(value)`**: Set a custom value for the `Server` header.
- **`clear()`**: Clear any custom value and revert the header to its default secure value (an empty string).

## Example Usage

To set up the `Server` header and hide the server information:

```python
from secure import Server

server_header = Server().set("")
print(server_header.header_name)   # Output: 'Server'
print(server_header.header_value)  # Output: ''
```

Then pass it into `Secure`:

```python
from secure import Secure

secure_headers = Secure(server=server_header)
```

### Special Considerations for Frameworks

Some frameworks like Uvicorn automatically inject a `Server` header. If you're using Uvicorn and need to override or remove this header, refer to the [framework integration guide](../frameworks.md) for specific instructions on how to disable Uvicorn's default `Server` header.

## **Resources**

- [MDN Web Docs: Server Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server)
- [OWASP Secure Headers Project: Server Header](https://owasp.org/www-project-secure-headers/#server-header)

## **Attribution**

This library implements security recommendations from trusted sources:

- [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/#server-header) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/headers/strict_transport_security.md
================================================
# Strict-Transport-Security (HSTS)

## What it does

The `Strict-Transport-Security` (HSTS) header tells browsers that a host **must only be accessed over HTTPS**. Once a browser has received this header, it will automatically upgrade future HTTP navigations to HTTPS for the configured duration, helping prevent man-in-the-middle and downgrade attacks.

> Important: Browsers **ignore** `Strict-Transport-Security` if it is delivered over **insecure HTTP**. You must send it over HTTPS only.

## Minimal example

```python
from secure import Secure, StrictTransportSecurity

secure_headers = Secure(
    hsts=StrictTransportSecurity().max_age(31536000).include_subdomains()
)
```

## Resulting header

```http
Strict-Transport-Security: max-age=31536000; includeSubDomains
```

## Practical note

Do not enable `includeSubDomains` until every subdomain is HTTPS-ready. Treat `preload()` as a deliberate rollout step because removal is slow once a domain is on the preload list.

## Default behavior

If you do not configure any directives, this library emits the default header value:

- **Default header value:** `max-age=31536000` (one year)

## Best practices

- **Use a long `max-age`**: One year (`31536000` seconds) is a common baseline.
- **Include subdomains (carefully)**: Add `includeSubDomains` only if _all_ subdomains are HTTPS-ready.
- **Only use `preload` when you mean it**:
  - `preload` is intended for submitting your domain to the HSTS preload list.
  - When using `preload`, the library enforces MDN’s requirements:
    - `max-age` must be **at least 31536000**
    - `includeSubDomains` must be present

## Configuration with `Secure`

Use `StrictTransportSecurity` for fluent, chainable configuration.

`Preset.BASIC` and `Preset.BALANCED` use one year with `includeSubDomains`; `Preset.STRICT` uses two years with `includeSubDomains`.

### Preload configuration

If you opt into preload, the library ensures preload requirements are satisfied:

```python
from secure import StrictTransportSecurity

hsts = (
    StrictTransportSecurity()
    .max_age(31536000)
    .include_subdomains()
    .preload()
)

print(hsts.header_name)   # 'Strict-Transport-Security'
print(hsts.header_value)  # 'max-age=31536000; includeSubDomains; preload'
```

If `preload()` is enabled with a `max-age` less than `31536000`, the header builder will raise a `ValueError`.

## Methods available

- **`max_age(seconds)`**
  Set `max-age`: how long (in seconds) the browser should remember to only use HTTPS for this host.

- **`include_subdomains()`**
  Add `includeSubDomains`: apply the HSTS policy to all subdomains as well.

- **`preload()`**
  Add `preload`: indicates intent to meet HSTS preload requirements. This library:
  - automatically enables `includeSubDomains`
  - enforces `max-age >= 31536000`

- **`clear()`**
  Clear configured directives and reset back to the library default behavior.

- **`value(str)` / `set(str)`**
  Escape hatch: set a raw header value (replaces any configured directives). The value must not contain CR/LF characters.

## Example usage

Minimal one-year HSTS:

```python
from secure import StrictTransportSecurity

hsts = StrictTransportSecurity().max_age(31536000)
print(hsts.header_value)  # 'max-age=31536000'
```

One-year HSTS including subdomains:

```python
from secure import StrictTransportSecurity

hsts = StrictTransportSecurity().max_age(31536000).include_subdomains()
print(hsts.header_value)  # 'max-age=31536000; includeSubDomains'
```

## Resources

- MDN Web Docs: Strict-Transport-Security
  [https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security)
- OWASP Secure Headers Project
  [https://owasp.org/www-project-secure-headers/](https://owasp.org/www-project-secure-headers/)
- HSTS Preload List
  [https://hstspreload.org/](https://hstspreload.org/)

## Attribution

This library implements security recommendations from trusted sources:

- MDN Web Docs: Strict-Transport-Security (licensed under CC-BY-SA 2.5)
  [https://creativecommons.org/licenses/by-sa/2.5/](https://creativecommons.org/licenses/by-sa/2.5/)
- OWASP Secure Headers Project (licensed under CC-BY-SA 4.0)
  [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)


================================================
FILE: docs/headers/x-permitted-cross-domain-policies.md
================================================
# X-Permitted-Cross-Domain-Policies

## What it does

`X-Permitted-Cross-Domain-Policies` is a **response header** that sets a _meta-policy_ controlling whether site resources can be accessed cross-origin by documents running in legacy web clients (for example, Adobe Acrobat or Microsoft Silverlight).

Usage is less common today because Flash/Silverlight have been deprecated, but many security testing tools still check for `X-Permitted-Cross-Domain-Policies: none` to reduce the risk of an overly-permissive cross-domain policy being present accidentally or maliciously.

> This documentation format mirrors the style used in the existing header docs (e.g., Cache-Control).

## Minimal example

```python
from secure import Secure, XPermittedCrossDomainPolicies

secure_headers = Secure(
    xpcdp=XPermittedCrossDomainPolicies().none()
)
```

## Resulting header

```http
X-Permitted-Cross-Domain-Policies: none
```

## Practical note

Most modern apps do not need this header, but security scanners still look for it. Add it when you want an explicit deny policy for legacy cross-domain policy files.

## Default behavior

If you create `XPermittedCrossDomainPolicies()` and do not set a policy, it returns the library default value:

- **Default header value:** `none`

This is the least permissive option and is the most common secure setting when you do not need legacy cross-domain policy behavior.

## Using with `Secure`

If you don’t configure anything, the default value is emitted.
`Preset.BASIC` includes `X-Permitted-Cross-Domain-Policies: none`; `Preset.BALANCED` and `Preset.STRICT` leave it out unless you add it explicitly.

## Common recipes

### 1) Disallow cross-domain policy files (recommended default)

```python
from secure import XPermittedCrossDomainPolicies

xpcdp = XPermittedCrossDomainPolicies()  # default: none
print(xpcdp.header_name)   # X-Permitted-Cross-Domain-Policies
print(xpcdp.header_value)  # none
```

MDN notes this is the typical configuration when you don’t need legacy clients.

### 2) Allow only a master policy file

```python
xpcdp = XPermittedCrossDomainPolicies().master_only()
print(xpcdp.header_value)  # master-only
```

This allows cross-domain access to the master policy file defined on the same domain.

### 3) Constrain policy files by content type (HTTP/HTTPS only)

```python
xpcdp = XPermittedCrossDomainPolicies().by_content_type()
print(xpcdp.header_value)  # by-content-type
```

Only policy files served with `Content-Type: text/x-cross-domain-policy` are allowed.

### 4) Indicate this response should not be treated as a policy file

```python
xpcdp = XPermittedCrossDomainPolicies().none_this_response()
print(xpcdp.header_value)  # none-this-response
```

This directive is unique to the HTTP header and indicates the current document should not be used as a policy file.

## Builder API

### Policy directives (single value)

- `.none()`
- `.master_only()`
- `.by_content_type()` (HTTP/HTTPS only)
- `.by_ftp_filename()` (FTP only)
- `.all()`
- `.none_this_response()` (HTTP-header-only)

These map directly to the directive definitions described by MDN (and largely echoed by OWASP).

### Typed helper

- `.policy("none" | "master-only" | "by-content-type" | "by-ftp-filename" | "all" | "none-this-response")`

Raises `ValueError` for unsupported values (helps catch typos early).

## Escape hatches

### `.value("...")`

Set an explicit header value (replaces any configured directive):

```python
xpcdp = XPermittedCrossDomainPolicies().value("none")
print(xpcdp.header_value)  # none
```

### `.custom("token")`

Alias for `.value(...)` (use when you intentionally want a raw string value):

```python
xpcdp = XPermittedCrossDomainPolicies().custom("master-only")
print(xpcdp.header_value)  # master-only
```

### `.clear()`

Reset to the default:

```python
xpcdp = XPermittedCrossDomainPolicies().all().clear()
print(xpcdp.header_value)  # none
```

## Deterministic output & safety

- The header value is rendered as a **single directive token**, so output is inherently deterministic.
- Raw setters (`.value(...)` / `.custom(...)`) normalize obvious header-splitting primitives (CR/LF) before serialization; stricter validation can be enforced via `Secure.validate_and_normalize_headers(...)`.

## Attribution

This library implements security recommendations and behavior described by:

- [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Permitted-Cross-Domain-Policies) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/#x-permitted-cross-domain-policies) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/headers/x_content_type_options.md
================================================
# X-Content-Type-Options

## What it does

The `X-Content-Type-Options` header tells browsers to **respect the MIME type declared in `Content-Type`** instead of trying to guess ("sniff") a different type.

In practice, setting `X-Content-Type-Options: nosniff` can cause browsers to **block**:

- `style` requests not served as `text/css`
- `script` requests not served with a JavaScript MIME type

This helps reduce the risk of content being interpreted as executable when it should not be.

## Minimal example

```python
from secure import Secure, XContentTypeOptions

secure_headers = Secure(
    xcto=XContentTypeOptions().nosniff(),
)
```

## Resulting header

```http
X-Content-Type-Options: nosniff
```

## Practical note

`nosniff` can expose incorrect MIME types in your app or asset pipeline. If enabling it breaks assets, fix the response `Content-Type` rather than weakening the header.

## Best Practices

- **Set to `nosniff`** (recommended): This is the standard and widely supported directive.
- **Use correct `Content-Type` values**: `nosniff` is most effective when your server sends accurate MIME types.

## Configuration with `Secure`

The `XContentTypeOptions` class configures `X-Content-Type-Options`.

**Default header value:** `nosniff`
All built-in presets include it.

### Methods available

- **`nosniff()`**: Sets the header to `nosniff`, which blocks certain `script`/`style` requests when MIME types are incorrect.
- **`set(value)` / `value(value)`**: Sets a raw/custom header value (escape hatch). `value` is an alias for `set`.
- **`clear()`**: Resets the header to the library default (`nosniff`).

> Note: `set/value` are escape hatches. If you use `Secure.validate_and_normalize_headers(...)`, that layer is responsible for sanitization and safety checks.

## Example usage

```python
from secure import XContentTypeOptions

xcto = XContentTypeOptions().nosniff()
print(xcto.header_name)   # 'X-Content-Type-Options'
print(xcto.header_value)  # 'nosniff'
```

Apply via `Secure`:

```python
from secure import Secure, XContentTypeOptions

secure_headers = Secure(xcto=XContentTypeOptions().nosniff())
```

## Resources

- MDN Web Docs: X-Content-Type-Options
  [https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)
- OWASP Secure Headers Project: X-Content-Type-Options
  [https://owasp.org/www-project-secure-headers/#x-content-type-options](https://owasp.org/www-project-secure-headers/#x-content-type-options)

## Attribution

This library implements security recommendations from trusted sources:

- MDN Web Docs (CC-BY-SA 2.5)
  [https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor](https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor)
  [https://creativecommons.org/licenses/by-sa/2.5/](https://creativecommons.org/licenses/by-sa/2.5/)
- OWASP Secure Headers Project (CC-BY-SA 4.0)
  [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)


================================================
FILE: docs/headers/x_frame_options.md
================================================
# X-Frame-Options

## What it does

`X-Frame-Options` is an HTTP **response header** that tells supporting browsers whether a page is allowed to render inside a `<frame>`, `<iframe>`, `<embed>`, or `<object>`. It is commonly used to reduce **clickjacking** risk by preventing (or restricting) framing.

> Prefer `Content-Security-Policy: frame-ancestors ...` for modern, more flexible control. `X-Frame-Options` is kept for compatibility with older clients and simpler deployments.

**Default header value:** `SAMEORIGIN`

## Minimal example

```python
from secure import Secure, XFrameOptions

secure_headers = Secure(xfo=XFrameOptions().sameorigin())
```

## Resulting header

```http
X-Frame-Options: SAMEORIGIN
```

## Practical note

Prefer CSP `frame-ancestors` for modern framing control. Keep `X-Frame-Options` as a compatibility layer, especially when you need support for older clients.

## Important notes

- **CSP is the modern replacement:** For comprehensive framing control, use CSP `frame-ancestors` (recommended).
- **`<meta http-equiv="X-Frame-Options" ...>` does nothing:** Browsers enforce `X-Frame-Options` only when it is sent as an HTTP response header.

## Directives

### `DENY`

The page **cannot** be displayed in a frame, regardless of what site is attempting to frame it (including the same site).

### `SAMEORIGIN`

The page can be displayed only if **all ancestor frames** have the **same origin** as the page itself.

### `ALLOW-FROM <origin>` (obsolete)

This directive is **obsolete**. Modern browsers that encounter `ALLOW-FROM` may **ignore the header completely**. Use CSP `frame-ancestors` instead.

## Using this library

### Minimal usage

The example above applies the default `SAMEORIGIN` behavior through `Secure`.

### Choose a directive

```python
from secure import XFrameOptions

xfo = XFrameOptions().deny()
print(xfo.header_name)   # 'X-Frame-Options'
print(xfo.header_value)  # 'DENY'
```

### Escape hatches

If you already have a fully-formed value, set it directly:

```python
from secure import XFrameOptions

xfo = XFrameOptions().value("SAMEORIGIN")
# Aliases (for compatibility / readability):
xfo = XFrameOptions().set("SAMEORIGIN")
xfo = XFrameOptions().custom("SAMEORIGIN")
```

Reset to the library default:

```python
xfo = XFrameOptions().deny().clear()
print(xfo.header_value)  # 'SAMEORIGIN'
```

### Obsolete directive (not recommended)

```python
from secure import XFrameOptions

# Warning: obsolete; prefer CSP frame-ancestors
xfo = XFrameOptions().allow_from("https://example.com")
```

## How it fits with presets

The built-in presets include `X-Frame-Options` by default:

- `Preset.BASIC` / `Preset.BALANCED`: `SAMEORIGIN`
- `Preset.STRICT`: `DENY`

If you want full modern control, keep CSP `frame-ancestors` and treat `X-Frame-Options` as a compatibility layer.

## Resources

- [MDN: X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options)
- [MDN: Clickjacking](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Clickjacking)
- [OWASP Secure Headers Project: X-Frame-Options](https://owasp.org/www-project-secure-headers/#x-frame-options)

## Attribution

This library implements security recommendations from trusted sources:

- MDN Web Docs (CC-BY-SA 2.5): [https://creativecommons.org/licenses/by-sa/2.5/](https://creativecommons.org/licenses/by-sa/2.5/)
- OWASP Secure Headers Project (CC-BY-SA 4.0): [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)


================================================
FILE: docs/installation.md
================================================
# Installation

`secure` requires Python 3.10 or newer and has no external dependencies.

Install it with `uv` or `pip`:

```bash
uv add secure
```

```bash
pip install secure
```

If you are following a framework example, install that framework separately.

```bash
pip install fastapi
```

After installation, import the public API from the package root:

```python
from secure import Secure
```

Most public builders are also re-exported from the package root, so you can usually keep imports in the form `from secure import Secure, Preset, ContentSecurityPolicy`.


================================================
FILE: docs/migration.md
================================================
# v2 Migration Notes

The first stable v2 release is `2.0.1`. Skip `2.0.0`.

If your application already uses `Secure` to set headers on responses, the upgrade should be straightforward. Most changes are about preset names, package-level imports, and clearer sync versus async integration.

## What stayed the same

- `Secure` is still the main entry point.
- Header builders such as `ContentSecurityPolicy` and `StrictTransportSecurity` are still the way to define custom policies.
- `set_headers(response)` is still the sync path for supported response objects.

## What changed

- Import from the package root: `from secure import Secure, Preset, ContentSecurityPolicy`.
- `Secure.with_default_headers()` now means `Secure.from_preset(Preset.BALANCED)`.
- Presets are now `Preset.BALANCED`, `Preset.BASIC`, and `Preset.STRICT`.
- `Preset.BALANCED` is the recommended default. `Preset.BASIC` is the compatibility-oriented option. `Preset.STRICT` is not the default.
- `set_headers_async(response)` is available for async integrations and async response setters.
- `secure.middleware` exposes `SecureWSGIMiddleware` and `SecureASGIMiddleware` for app-wide integration.

## What might break

- `Preset.MODERN` is gone. Replace it with `Preset.BALANCED` for the new default or `Preset.STRICT` if you specifically want a tighter profile.
- The default profile is now `BALANCED`, which intentionally omits `Cache-Control` and the legacy compatibility headers from `BASIC`.
- `Preset.STRICT` no longer enables HSTS preload by default. Add `.preload()` yourself if you rely on that behavior.
- `set_headers()` is sync-only. If your response object only supports async setters, switch to `await set_headers_async(response)`.
- If you set the `Server` header, disable framework or server defaults such as Uvicorn's `Server: uvicorn` to avoid duplicates.

## Minimal upgrade path

If you previously relied on the default helpers, this is usually enough:

```python
from secure import Secure

secure_headers = Secure.with_default_headers()
secure_headers.set_headers(response)
```

If you want the new preset API explicitly:

```python
from secure import Preset, Secure

secure_headers = Secure.from_preset(Preset.BALANCED)
```

If your old code expected stricter defaults, review `Preset.STRICT` before switching. The main thing to check is CSP behavior, caching, framing, and HSTS preload.


================================================
FILE: docs/security_considerations.md
================================================
# Security Considerations

## Overview

Security headers are one part of a web application's security posture. They help browsers enforce transport, embedding, content loading, and privacy rules, but they do not replace application-layer controls such as output encoding, CSRF protection, authentication, or input validation.

This guide keeps the advice tied to what `secure` actually emits and where the tradeoffs are operational rather than theoretical.

## Importance of Security Headers

### **Strict-Transport-Security (HSTS)**

The `Strict-Transport-Security` header ensures that browsers only connect to your site over HTTPS, preventing MITM attacks by forcing a secure connection. It tells the browser to remember to always access the site via HTTPS, even if the user tries to access it over HTTP.

- [MDN Docs - Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
- **Best Practice**: Use a long `max-age` and include subdomains only when every subdomain is HTTPS-ready.
- **Pitfall**: Be cautious when setting the `preload` directive, as it’s difficult to remove once added to the HSTS preload list.

---

### **Content-Security-Policy (CSP)**

The `Content-Security-Policy` header limits which sources the browser will trust for scripts, styles, images, frames, and other resource types. A well-tuned CSP reduces the impact of XSS and unsafe third-party content, but the policy still has to match how your frontend actually loads code and assets.

- [MDN Docs - Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
- **Best Practice**: Start with a restrictive baseline and expand only where the application requires it. Use nonces or hashes for inline scripts when possible.
- **Pitfall**: Overly permissive CSP rules such as `unsafe-inline`, `unsafe-eval`, or broad allowlists can leave your application vulnerable to XSS attacks.
- **Library note**: `Preset.BALANCED` allows `'unsafe-inline'` in `style-src` for compatibility. It does not allow inline scripts by default. If you add `'unsafe-inline'` to `script-src`, treat it as an app-specific compatibility change and test the real app before rollout.

---

### **X-Frame-Options**

The `X-Frame-Options` header prevents clickjacking by controlling whether a page can be framed. In modern deployments, treat it as a compatibility header alongside CSP `frame-ancestors`.

- [MDN Docs - X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)
- **Best Practice**: Set to `DENY` to completely block framing, or `SAMEORIGIN` if you only want to allow framing from your own domain.
- **Pitfall**: Be careful when setting `SAMEORIGIN` if you allow content embedding. Incorrect settings can break legitimate functionality, such as embedded dashboards or widgets.

---

### **X-Content-Type-Options**

The `X-Content-Type-Options` header prevents MIME-sniffing by telling browsers to respect the declared `Content-Type`. In practice, it is most relevant for blocking incorrectly typed script and stylesheet responses.

- [MDN Docs - X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)
- **Best Practice**: Always set this header to `nosniff`.
- **Pitfall**: This header can surface incorrect `Content-Type` handling in your app or asset pipeline.

---

### **Referrer-Policy**

The `Referrer-Policy` header controls how much referrer information is included with requests. By limiting referrer data, you can prevent sensitive URL data from being exposed to third-party sites.

- [MDN Docs - Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)
- **Best Practice**: Use `strict-origin-when-cross-origin` to protect sensitive referrer information while preserving analytics functionality.
- **Pitfall**: Using `unsafe-url` can expose full URLs, which may leak sensitive data.

---

### **Permissions-Policy**

The `Permissions-Policy` header allows you to disable or scope browser features such as geolocation, camera access, and microphone access.

- [MDN Docs - Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy)
- **Best Practice**: Disable unnecessary features (e.g., `camera`, `microphone`, `geolocation`) to reduce attack surface.
- **Pitfall**: Incorrectly blocking required features may break functionality like video conferencing or map-based services.

---

### **Cross-Origin-Embedder-Policy (COEP)**

The `Cross-Origin-Embedder-Policy` header controls whether a document can load cross-origin resources that do not explicitly opt in via CORP or CORS.

- [MDN Docs - Cross-Origin-Embedder-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy)
- **Best Practice**: Use COEP when you need cross-origin isolation and can verify that your own and third-party resources are compatible.
- **Pitfall**: Misconfiguration often breaks legitimate cross-origin assets before it improves anything.

---

### **Cross-Origin-Opener-Policy (COOP)**

The `Cross-Origin-Opener-Policy` header isolates a document's browsing context group, which helps reduce XS-Leaks and is typically paired with COEP when you need cross-origin isolation.

- [MDN Docs - Cross-Origin-Opener-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy)
- **Best Practice**: Set this to `same-origin` to protect against XS-Leaks and ensure that only same-origin documents can access the browsing context.
- **Pitfall**: Popups, payment flows, or OAuth-style integrations may need a less strict value than `same-origin`.

---

### **Cache-Control**

The `Cache-Control` header controls how responses are cached. For security-sensitive responses, it helps prevent browsers and intermediaries from storing content that should not persist.

- [MDN Docs - Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
- **Best Practice**: Use `no-store` for sensitive pages like login or payment forms to ensure that they are not cached.
- **Pitfall**: Improper caching of sensitive data can lead to exposure of private information.

---

### **Server**

The `Server` header can disclose software details, but changing or clearing it should be treated as passive information reduction, not as a primary defense.

- [MDN Docs - Server](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server)
- **Best Practice**: Set a generic or empty value when your stack allows it, and disable framework or proxy defaults that would re-add a value upstream.
- **Pitfall**: Do not assume hiding `Server` materially hardens a vulnerable application.

---

### **Custom Headers**

`CustomHeader` is an escape hatch for application-specific response headers. Use it when you need to emit a header that does not have a dedicated builder, but keep the semantics and deployment expectations documented elsewhere in your application.

---

## Common Pitfalls

- **Improper CSP configurations**: Using `unsafe-inline`, `unsafe-eval`, or broad source allowlists weakens CSP quickly.
- **Weak HSTS rollout discipline**: Sending HSTS before all routes and subdomains are HTTPS-ready can break access just as easily as it improves transport security.

## OWASP Guidelines

For further recommendations on security headers, refer to the [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/).

---

## **Attribution**

This library implements security recommendations from trusted sources:

- [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) (licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))
- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/) (licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/))


================================================
FILE: docs/usage.md
================================================
# Usage

`Secure` is the public entry point. Configure it once, then reuse it wherever your framework gives you access to the response.

## Start with a preset

`Secure.with_default_headers()` is the recommended starting point. It matches `Preset.BALANCED`.

```python
from secure import Secure

secure_headers = Secure.with_default_headers()
```

You can also choose a preset explicitly:

```python
from secure import Preset, Secure

balanced = Secure.from_preset(Preset.BALANCED)
basic = Secure.from_preset(Preset.BASIC)
strict = Secure.from_preset(Preset.STRICT)
```

- `Preset.BALANCED`: recommended default for most applications.
- `Preset.BASIC`: Helmet-style compatibility.
- `Preset.STRICT`: tighter CSP, cross-origin isolation headers, disabled caching, and stricter framing rules.

## Apply headers to a response

Use `set_headers()` for synchronous response objects:

```python
from secure import Secure

secure_headers = Secure.with_default_headers()


def add_security_headers(response):
    secure_headers.set_headers(response)
    return response
```

Use `set_headers_async()` in async code or when the response object may expose async setters:

```python
from secure import Secure

secure_headers = Secure.with_default_headers()


async def add_security_headers(response):
    await secure_headers.set_headers_async(response)
    return response
```

`Secure` works with response objects that provide either:

- `response.set_header(name, value)`
- `response.headers[name] = value`

If your framework uses a different contract, emit headers manually with `header_items()`.

## App-wide middleware

If you want application-wide coverage, use the middleware classes from `secure.middleware`.

- Both `SecureASGIMiddleware` and `SecureWSGIMiddleware` overwrite configured header names by default.
- Use `multi_ok` when you intentionally want controlled duplication instead of overwrite.
- `SecureASGIMiddleware` only modifies HTTP responses. WebSocket and other non-HTTP scopes pass through unchanged.

```python
from secure import Secure
from secure.middleware import SecureASGIMiddleware

secure_headers = Secure.with_default_headers()
secured_app = SecureASGIMiddleware(app, secure=secure_headers)
```

If you need multiple `Content-Security-Policy` headers, keep those names in `multi_ok` and emit the policy values you intend to preserve.

## Build an explicit configuration

Presets are the shortest path. When you need more control, pass builder objects into `Secure`.

```python
from secure import (
    ContentSecurityPolicy,
    PermissionsPolicy,
    Secure,
    StrictTransportSecurity,
)

secure_headers = Secure(
    csp=(
        ContentSecurityPolicy()
        .default_src("'self'")
        .img_src("'self'", "https://images.example.com")
        .script_src("'self'", "https://cdn.example.com")
    ),
    hsts=StrictTransportSecurity().max_age(63072000).include_subdomains(),
    permissions=PermissionsPolicy().geolocation().microphone().camera(),
)
```

This keeps the configuration readable while avoiding hand-built header strings.
Stricter CSP changes should always be tested against the real application before rollout.

## Optional validation pipeline

Most applications do not need this. It is useful when headers are being merged, extended, or generated dynamically and you want validation before emission.

```python
from secure import Secure

secure_headers = (
    Secure.with_default_headers()
    .allowlist_headers()
    .deduplicate_headers()
    .validate_and_normalize_headers()
)
```

After `validate_and_normalize_headers()`, the normalized mapping is available via `secure_headers.headers`.

## Manual emission

Use `header_items()` when a framework does not expose a supported response interface or when you need ordered header pairs.

```python
from secure import Secure

secure_headers = Secure.with_default_headers()

for name, value in secure_headers.header_items():
    response.headers[name] = value
```

For framework-specific examples, see [Framework Integration](./frameworks.md).


================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "secure"
version = "2.0.1"
description = "A lightweight package that adds security headers for Python web frameworks."
readme = { file = "README.md", content-type = "text/markdown" }
license = "MIT"
license-files = ["LICENSE"]
authors = [{ name = "Caleb Kinney", email = "caleb@typeerror.com" }]
requires-python = ">=3.10"
keywords = ["security", "headers", "web", "framework", "HTTP"]
classifiers = [
  "Development Status :: 5 - Production/Stable",
  "Intended Audience :: Developers",
  "Operating System :: OS Independent",
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
  "Typing :: Typed",
  "Topic :: Software Development :: Libraries",
]
dependencies = []

[project.urls]
Homepage = "https://github.com/TypeError/secure"
Documentation = "https://github.com/TypeError/secure/tree/main/docs"
Repository = "https://github.com/TypeError/secure"
"Issue Tracker" = "https://github.com/TypeError/secure/issues"

# --- Setuptools package discovery ---
[tool.setuptools.packages.find]
include = ["secure*"]
exclude = ["tests*", "docs*"]

[tool.setuptools.package-data]
secure = ["py.typed"]

[tool.pytest.ini_options]
pythonpath = ["."]
testpaths = ["tests"]

# --- Ruff (formatter + linter) ---
[tool.ruff]
target-version = "py310"
line-length = 120
src = ["secure"]
exclude = ["build", "dist", ".venv", "**/__pycache__"]
fix = true

[tool.ruff.format]
quote-style = "preserve"
indent-style = "space"
docstring-code-format = true

[tool.ruff.lint]
select = [
  # Core
  "E","F",
  # Import sorting (isort)
  "I",
  # Upgrades & quality
  "UP","B","C4","SIM",
  # Typing / type-checking hygiene
  "ANN","TCH",
  # Misc rule families used below
  "DTZ","PTH","T20","ERA","ISC","TRY","S","PERF","N","Q","PL","RUF"
]
ignore = ["TRY003", "EM101"]


[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101","S311","T20","PLR2004", "ANN001", "ANN202", "ANN401", "ANN201"]
"examples/**" = ["T20"]

[tool.ruff.lint.flake8-annotations]
mypy-init-return = true
suppress-dummy-args = true
suppress-none-returning = false
allow-star-arg-any = false
ignore-fully-untyped = false

[tool.ruff.lint.flake8-type-checking]
exempt-modules = ["typing", "typing_extensions"]
runtime-evaluated-base-classes = ["pydantic.BaseModel"]

[tool.ruff.lint.isort]
known-first-party = ["secure"]
combine-as-imports = true
force-sort-within-sections = true

[tool.ruff.lint.pydocstyle]
convention = "google"


================================================
FILE: secure/__init__.py
================================================
from secure.secure import (
    COMMA_JOIN_OK,
    DEFAULT_ALLOWED_HEADERS,
    MULTI_OK,
    Preset,
    Secure,
)

from .headers.cache_control import CacheControl
from .headers.content_security_policy import ContentSecurityPolicy
from .headers.cross_origin_embedder_policy import CrossOriginEmbedderPolicy
from .headers.cross_origin_opener_policy import CrossOriginOpenerPolicy
from .headers.cross_origin_resource_policy import CrossOriginResourcePolicy
from .headers.custom_header import CustomHeader
from .headers.permissions_policy import PermissionsPolicy
from .headers.referrer_policy import ReferrerPolicy
from .headers.server import Server
from .headers.strict_transport_security import StrictTransportSecurity
from .headers.x_content_type_options import XContentTypeOptions
from .headers.x_dns_prefetch_control import XDnsPrefetchControl
from .headers.x_frame_options import XFrameOptions
from .headers.x_permitted_cross_domain_policies import XPermittedCrossDomainPolicies

__all__ = [
    "COMMA_JOIN_OK",
    "DEFAULT_ALLOWED_HEADERS",
    "MULTI_OK",
    "CacheControl",
    "ContentSecurityPolicy",
    "CrossOriginEmbedderPolicy",
    "CrossOriginOpenerPolicy",
    "CrossOriginResourcePolicy",
    "CustomHeader",
    "PermissionsPolicy",
    "Preset",
    "ReferrerPolicy",
    "Secure",
    "Server",
    "StrictTransportSecurity",
    "XContentTypeOptions",
    "XDnsPrefetchControl",
    "XFrameOptions",
    "XPermittedCrossDomainPolicies",
]


================================================
FILE: secure/_internal/__init__.py
================================================
"""Private implementation details for the secure package."""


================================================
FILE: secure/_internal/configured_headers.py
================================================
from __future__ import annotations

from types import MappingProxyType
from typing import TYPE_CHECKING, Any, TypeAlias, overload

from ..headers.base_header import BaseHeader
from ..headers.custom_header import CustomHeader
from .types import HeaderItems, HeaderPair

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable

HeaderInput: TypeAlias = BaseHeader | HeaderPair | list[str]
HEADER_PAIR_SIZE = 2


class ConfiguredHeaders(list[BaseHeader]):
    """Mutable header builder collection with centralized mutation bookkeeping."""

    def __init__(
        self,
        headers: Iterable[HeaderInput] = (),
        *,
        on_change: Callable[[], None],
    ) -> None:
        self._on_change = on_change
        super().__init__(_coerce_header_object(header, operation="ConfiguredHeaders") for header in headers)

    def replace_all(self, headers: Iterable[HeaderInput]) -> None:
        replacement = [_coerce_header_object(header, operation="headers_list") for header in headers]
        super().clear()
        super().extend(replacement)
        self._on_change()

    def append(self, header: HeaderInput) -> None:
        super().append(_coerce_header_object(header, operation="headers_list.append"))
        self._on_change()

    def extend(self, headers: Iterable[HeaderInput]) -> None:
        super().extend(_coerce_header_object(header, operation="headers_list.extend") for header in headers)
        self._on_change()

    def insert(self, index: int, header: HeaderInput) -> None:
        super().insert(index, _coerce_header_object(header, operation="headers_list.insert"))
        self._on_change()

    @overload
    def __setitem__(self, index: int, header: HeaderInput) -> None: ...

    @overload
    def __setitem__(self, index: slice, header: Iterable[HeaderInput]) -> None: ...

    def __setitem__(self, index: int | slice, header: HeaderInput | Iterable[HeaderInput]) -> None:
        if isinstance(index, slice):
            super().__setitem__(
                index,
                [_coerce_header_object(item, operation="headers_list assignment") for item in header],
            )
        else:
            super().__setitem__(index, _coerce_header_object(header, operation="headers_list assignment"))
        self._on_change()

    def __delitem__(self, index: int | slice) -> None:
        super().__delitem__(index)
        self._on_change()

    def clear(self) -> None:
        super().clear()
        self._on_change()

    def pop(self, index: int = -1) -> BaseHeader:
        header = super().pop(index)
        self._on_change()
        return header

    def remove(self, header: BaseHeader) -> None:
        super().remove(header)
        self._on_change()

    def reverse(self) -> None:
        super().reverse()
        self._on_change()

    def sort(self, *, key: Callable[[BaseHeader], Any] | None = None, reverse: bool = False) -> None:
        super().sort(key=key, reverse=reverse)
        self._on_change()

    def __iadd__(self, headers: Iterable[HeaderInput]) -> ConfiguredHeaders:
        self.extend(headers)
        return self


def header_items_from_objects(headers: Iterable[BaseHeader]) -> HeaderItems:
    return tuple((header.header_name, header.header_value) for header in headers)


def header_mapping_from_items(items: HeaderItems) -> MappingProxyType[str, str]:
    headers: dict[str, str] = {}
    seen_names: set[str] = set()

    for name, value in items:
        lowered_name = name.lower()
        if lowered_name in seen_names:
            raise ValueError(f"Multiple '{name}' headers present; use `header_items()` when emitting multiples.")
        seen_names.add(lowered_name)
        headers[name] = value

    return MappingProxyType(headers)


def _coerce_header_object(header: HeaderInput, *, operation: str) -> BaseHeader:
    if isinstance(header, BaseHeader):
        return header

    if (isinstance(header, tuple) and len(header) == HEADER_PAIR_SIZE) or (
        isinstance(header, list) and len(header) == HEADER_PAIR_SIZE
    ):
        name, value = header
        if isinstance(name, str) and isinstance(value, str):
            return CustomHeader(header=name, value=value)

    raise TypeError(f"{operation} expects BaseHeader objects or (name, value) pairs")


================================================
FILE: secure/_internal/constants.py
================================================
from __future__ import annotations

import re

# Headers that may appear multiple times as separate fields.
MULTI_OK: frozenset[str] = frozenset(
    {
        "content-security-policy",
    }
)

# Headers where RFC7230-style comma merging is safe/expected.
COMMA_JOIN_OK: frozenset[str] = frozenset({"cache-control"})

# A default allowlist of secure headers.
DEFAULT_ALLOWED_HEADERS: frozenset[str] = frozenset(
    {
        "cache-control",
        "content-security-policy",
        "content-security-policy-report-only",
        "cross-origin-embedder-policy",
        "cross-origin-opener-policy",
        "cross-origin-resource-policy",
        "origin-agent-cluster",
        "permissions-policy",
        "referrer-policy",
        "server",
        "strict-transport-security",
        "x-content-type-options",
        "x-dns-prefetch-control",
        "x-download-options",
        "x-frame-options",
        "x-permitted-cross-domain-policies",
        "x-xss-protection",
    }
)

# RFC 7230 token (visible ASCII except separators).
HEADER_NAME_RE = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")


================================================
FILE: secure/_internal/emit.py
================================================
from __future__ import annotations

import inspect
from typing import TYPE_CHECKING, cast

if TYPE_CHECKING:
    from collections.abc import Callable, MutableMapping

    from .types import HeaderItems, ResponseProtocol


class HeaderSetError(RuntimeError):
    """Raised when applying a header to a response fails."""


def set_headers_sync(response: ResponseProtocol, items: HeaderItems) -> None:
    """
    Apply header items to a sync response object.
    """
    if hasattr(response, "set_header"):
        _apply_sync_setter(
            response.set_header,
            items,
            coroutine_function_error=(
                "Async 'set_header' detected in sync context. Use 'await set_headers_async(response)'."
            ),
            awaitable_error=(
                "Async 'set_header' returned awaitable in sync context. Use 'await set_headers_async(response)'."
            ),
        )
        return

    _set_headers_container_sync(_get_headers_container(response), items)


async def set_headers_async(response: ResponseProtocol, items: HeaderItems) -> None:
    """
    Apply header items to a sync or async response object from async code.
    """
    if hasattr(response, "set_header"):
        await _apply_async_setter(response.set_header, items)
        return

    await _set_headers_container_async(_get_headers_container(response), items)


def _get_headers_container(response: ResponseProtocol) -> object:
    if hasattr(response, "headers"):
        return cast("object", response.headers)
    raise AttributeError("Response object does not support setting headers.")


def _set_headers_container_sync(headers: object, items: HeaderItems) -> None:
    set_fn = getattr(headers, "set", None)
    if callable(set_fn):
        _apply_sync_setter(
            set_fn,
            items,
            coroutine_function_error=(
                "Async headers setter detected in sync context. Use 'await set_headers_async(response)'."
            ),
            awaitable_error=(
                "Async headers setter returned awaitable in sync context. Use 'await set_headers_async(response)'."
            ),
        )
        return

    _set_headers_mapping_sync(headers, items)


async def _set_headers_container_async(headers: object, items: HeaderItems) -> None:
    set_fn = getattr(headers, "set", None)
    if callable(set_fn):
        await _apply_async_setter(set_fn, items)
        return

    await _set_headers_mapping_async(headers, items)


def _apply_sync_setter(
    setter: object,
    items: HeaderItems,
    *,
    coroutine_function_error: str,
    awaitable_error: str,
) -> None:
    if inspect.iscoroutinefunction(setter):
        raise RuntimeError(coroutine_function_error)

    typed_setter = cast("Callable[[str, str], object]", setter)

    try:
        for name, value in items:
            result = typed_setter(name, value)
            if inspect.isawaitable(result):
                raise RuntimeError(awaitable_error)
    except (TypeError, ValueError, AttributeError) as error:
        raise HeaderSetError(f"Failed to set headers: {error}") from error


async def _apply_async_setter(setter: object, items: HeaderItems) -> None:
    typed_setter = cast("Callable[[str, str], object]", setter)

    try:
        for name, value in items:
            result = typed_setter(name, value)
            if inspect.isawaitable(result):
                await result
    except (TypeError, ValueError, AttributeError) as error:
        raise HeaderSetError(f"Failed to set headers: {error}") from error


def _set_headers_mapping_sync(headers: object, items: HeaderItems) -> None:
    setitem = getattr(headers, "__setitem__", None)
    if not callable(setitem):
        raise AttributeError(  # noqa: TRY004
            "Response object has .headers but it does not support setting header values."
        )

    if inspect.iscoroutinefunction(setitem):
        raise RuntimeError("Async headers mapping detected in sync context. Use 'await set_headers_async(response)'.")

    try:
        headers_map = cast("MutableMapping[str, str]", headers)
        for name, value in items:
            headers_map[name] = value
    except (TypeError, ValueError, AttributeError) as error:
        raise HeaderSetError(f"Failed to set headers: {error}") from error


async def _set_headers_mapping_async(headers: object, items: HeaderItems) -> None:
    setitem = getattr(headers, "__setitem__", None)
    if not callable(setitem):
        raise AttributeError(  # noqa: TRY004
            "Response object has .headers but it does not support setting header values."
        )

    await _apply_async_setter(setitem, items)


================================================
FILE: secure/_internal/normalize.py
================================================
from __future__ import annotations

from dataclasses import dataclass
import logging
from types import MappingProxyType
from typing import TYPE_CHECKING

from .constants import HEADER_NAME_RE

if TYPE_CHECKING:
    from collections.abc import Iterable, Mapping

    from .types import OnInvalidPolicy

SPACE_CODEPOINT = 0x20
VISIBLE_CHAR_MIN = 0x21
VISIBLE_CHAR_MAX = 0x7E
OBS_TEXT_MIN = 0x80
OBS_TEXT_MAX = 0xFF


@dataclass(frozen=True)
class _NormalizationOptions:
    on_invalid: OnInvalidPolicy
    strict: bool
    allow_obs_text: bool
    logger: logging.Logger


def normalize_header_items(
    items: Iterable[tuple[str, str]],
    *,
    on_invalid: OnInvalidPolicy = "drop",
    strict: bool = False,
    allow_obs_text: bool = False,
    logger: logging.Logger | None = None,
) -> Mapping[str, str]:
    """
    Validate and normalize header items into a single-valued immutable mapping.
    """
    options = _NormalizationOptions(
        on_invalid=on_invalid,
        strict=strict,
        allow_obs_text=allow_obs_text,
        logger=logger or logging.getLogger(__name__),
    )
    cleaned: dict[str, str] = {}
    seen_lc: set[str] = set()

    for name, value in items:
        pair = _validate_header_pair(
            name,
            value,
            options=options,
        )
        if pair is None:
            continue

        normalized_name, normalized_value = pair
        lowered_name = normalized_name.lower()

        if lowered_name in seen_lc:
            raise ValueError(
                f"Duplicate header {normalized_name!r} encountered during normalization. "
                "Run deduplicate_headers() first or use header_items() for multi-valued headers."
            )

        seen_lc.add(lowered_name)
        cleaned[normalized_name] = normalized_value

    return MappingProxyType(cleaned)


def _validate_header_pair(
    name: str,
    value: str,
    *,
    options: _NormalizationOptions,
) -> tuple[str, str] | None:
    normalized_name = name.strip()

    if not HEADER_NAME_RE.match(normalized_name):
        _handle_invalid(
            f"Invalid header name {normalized_name!r} (RFC 7230 token required)",
            options=options,
        )
        return None

    if value.startswith((" ", "\t")):
        _handle_invalid(
            f"Header {normalized_name!r} starts with forbidden whitespace",
            options=options,
        )
        return None

    normalized_value = value
    if ("\r" in normalized_value) or ("\n" in normalized_value):
        if options.strict:
            raise ValueError(f"Header {normalized_name!r} contained CR/LF")
        normalized_value = " ".join(normalized_value.splitlines())

    normalized_value = normalized_value.strip()
    if not normalized_value:
        _handle_invalid(
            f"Dropping header {normalized_name!r}: empty value",
            options=options,
        )
        return None

    if _value_is_allowed(normalized_value, allow_obs_text=options.allow_obs_text):
        return normalized_name, normalized_value

    sanitized_value = _sanitize_value(
        normalized_name,
        normalized_value,
        strict=options.strict,
        allow_obs_text=options.allow_obs_text,
    )
    if not sanitized_value:
        _handle_invalid(
            f"Dropping header {normalized_name!r}: empty after sanitization",
            options=options,
        )
        return None

    return normalized_name, sanitized_value


def _handle_invalid(message: str, *, options: _NormalizationOptions) -> None:
    if options.on_invalid == "warn":
        options.logger.warning(message)
    elif options.on_invalid == "raise":
        raise ValueError(message)


def _value_is_allowed(value: str, *, allow_obs_text: bool) -> bool:
    return all(_is_allowed_value_char(char, allow_obs_text=allow_obs_text) for char in value)


def _is_allowed_value_char(char: str, *, allow_obs_text: bool) -> bool:
    codepoint = ord(char)
    return (
        char == "\t"
        or codepoint == SPACE_CODEPOINT
        or VISIBLE_CHAR_MIN <= codepoint <= VISIBLE_CHAR_MAX
        or (allow_obs_text and OBS_TEXT_MIN <= codepoint <= OBS_TEXT_MAX)
    )


def _sanitize_value(name: str, value: str, *, strict: bool, allow_obs_text: bool) -> str:
    sanitized_characters: list[str] = []

    for char in value:
        codepoint = ord(char)
        if _is_allowed_value_char(char, allow_obs_text=allow_obs_text):
            sanitized_characters.append(char)
            continue

        if strict:
            raise ValueError(f"Header {name!r} contains disallowed char U+{codepoint:04X}")

        sanitized_characters.append(" ")

    return "".join(sanitized_characters).strip()


================================================
FILE: secure/_internal/policy.py
================================================
from __future__ import annotations

from collections import defaultdict
import logging
from typing import TYPE_CHECKING

from ..headers.base_header import BaseHeader
from ..headers.custom_header import CustomHeader

if TYPE_CHECKING:
    from collections.abc import Iterable

    from .types import DeduplicateAction, OnUnexpectedPolicy


def deduplicate_header_objects(
    headers: Iterable[BaseHeader],
    *,
    action: DeduplicateAction = "raise",
    comma_join_ok: frozenset[str],
    multi_ok: frozenset[str],
    logger: logging.Logger | None = None,
) -> list[BaseHeader]:
    """
    Deduplicate header objects while preserving stable header ordering.
    """
    log = logger or logging.getLogger(__name__)
    groups: dict[str, list[tuple[int, BaseHeader]]] = defaultdict(list)

    for index, header in enumerate(headers):
        validated_header = _require_header_object(header, operation="deduplicate_headers")
        groups[validated_header.header_name.lower()].append((index, validated_header))

    ordered_keys = sorted(groups.keys(), key=lambda key: groups[key][0][0])
    deduplicated_headers: list[BaseHeader] = []
    duplicate_errors: list[str] = []

    for lowered_name in ordered_keys:
        entries = groups[lowered_name]

        if len(entries) == 1:
            _, header = entries[0]
            deduplicated_headers.append(_clone_as_custom_header(header.header_name, header.header_value))
            continue

        if lowered_name in multi_ok:
            for _, header in entries:
                deduplicated_headers.append(_clone_as_custom_header(header.header_name, header.header_value))
            continue

        resolved_headers, error_name = _resolve_duplicate_headers(
            lowered_name,
            entries,
            action=action,
            comma_join_ok=comma_join_ok,
            logger=log,
        )
        deduplicated_headers.extend(resolved_headers)

        if error_name is not None:
            duplicate_errors.append(error_name)

    if duplicate_errors:
        names = ", ".join(sorted(set(duplicate_errors)))
        raise ValueError(f"Duplicate header(s) not allowed: {names}. Define each at most once.")

    return deduplicated_headers


def allowlist_header_objects(  # noqa: PLR0913
    headers: Iterable[BaseHeader],
    *,
    allowed: Iterable[str],
    allow_extra: Iterable[str] | None = None,
    on_unexpected: OnUnexpectedPolicy = "raise",
    allow_x_prefixed: bool = False,
    logger: logging.Logger | None = None,
) -> list[BaseHeader]:
    """
    Filter header objects against a case-insensitive allowlist.
    """
    log = logger or logging.getLogger(__name__)
    allowed_lc = {header.lower() for header in allowed}
    if allow_extra:
        allowed_lc.update(header.lower() for header in allow_extra)

    kept: list[BaseHeader] = []
    unexpected_names: list[str] = []

    for header in headers:
        validated_header = _require_header_object(header, operation="allowlist_headers")
        header_name = validated_header.header_name
        lowered_name = header_name.lower()

        if _is_allowed_header_name(
            lowered_name,
            allowed=allowed_lc,
            allow_x_prefixed=allow_x_prefixed,
        ):
            kept.append(validated_header)
            continue

        if on_unexpected == "warn":
            log.warning("Unexpected header %r kept (not in allowlist)", header_name)
            kept.append(validated_header)
        elif on_unexpected == "drop":
            log.warning("Unexpected header %r dropped (not in allowlist)", header_name)
        else:
            unexpected_names.append(header_name)

    if unexpected_names:
        names = ", ".join(sorted(set(unexpected_names)))
        raise ValueError(
            f"Unexpected header(s) not in allowlist: {names}. Enable allow_extra or set on_unexpected to 'drop'/'warn'."
        )

    return kept


def _require_header_object(header: object, *, operation: str) -> BaseHeader:
    if not isinstance(header, BaseHeader):
        raise TypeError(f"{operation}() requires BaseHeader objects only")
    return header


def _clone_as_custom_header(name: str, value: str) -> BaseHeader:
    return CustomHeader(header=name, value=value)


def _resolve_duplicate_headers(
    lowered_name: str,
    entries: list[tuple[int, BaseHeader]],
    *,
    action: DeduplicateAction,
    comma_join_ok: frozenset[str],
    logger: logging.Logger,
) -> tuple[list[BaseHeader], str | None]:
    if action == "first":
        _, header = entries[0]
        if len(entries) > 1:
            logger.warning("Dropping duplicate header(s) for %r (keeping first)", header.header_name)
        return [_clone_as_custom_header(header.header_name, header.header_value)], None

    if action == "last":
        _, header = entries[-1]
        if len(entries) > 1:
            logger.warning("Dropping duplicate header(s) for %r (keeping last)", header.header_name)
        return [_clone_as_custom_header(header.header_name, header.header_value)], None

    if action == "concat":
        if lowered_name in comma_join_ok:
            name = entries[0][1].header_name
            joined_value = ", ".join(header.header_value for _, header in entries)
            return [_clone_as_custom_header(name, joined_value)], None

        return [], entries[0][1].header_name

    return [], entries[0][1].header_name


def _is_allowed_header_name(
    lowered_name: str,
    *,
    allowed: set[str],
    allow_x_prefixed: bool,
) -> bool:
    return lowered_name in allowed or (allow_x_prefixed and lowered_name.startswith("x-"))


================================================
FILE: secure/_internal/presets.py
================================================
from __future__ import annotations

from enum import Enum

from ..headers import (
    CacheControl,
    ContentSecurityPolicy,
    CrossOriginEmbedderPolicy,
    CrossOriginOpenerPolicy,
    CrossOriginResourcePolicy,
    CustomHeader,
    PermissionsPolicy,
    ReferrerPolicy,
    Server,
    StrictTransportSecurity,
    XContentTypeOptions,
    XDnsPrefetchControl,
    XFrameOptions,
    XPermittedCrossDomainPolicies,
)


class Preset(Enum):
    """Predefined security header presets for :class:`Secure`."""

    BASIC = "basic"
    BALANCED = "balanced"
    STRICT = "strict"


def _baseline_content_security_policy() -> ContentSecurityPolicy:
    """Shared CSP builder used by the BASIC and BALANCED presets."""
    return (
        ContentSecurityPolicy()
        .default_src("'self'")
        .base_uri("'self'")
        .font_src("'self'", "https:", "data:")
        .form_action("'self'")
        .frame_ancestors("'self'")
        .img_src("'self'", "data:")
        .object_src("'none'")
        .script_src("'self'")
        .script_src_attr("'none'")
        .style_src("'self'", "https:", "'unsafe-inline'")
        .upgrade_insecure_requests()
    )


def preset_kwargs(preset: Preset) -> dict[str, object]:
    """Return constructor kwargs for a predefined :class:`Secure` preset."""
    match preset:
        case Preset.BASIC:
            return {
                "coop": CrossOriginOpenerPolicy().same_origin(),
                "csp": _baseline_content_security_policy(),
                "corp": CrossOriginResourcePolicy().same_origin(),
                "hsts": StrictTransportSecurity().max_age(31536000).include_subdomains(),
                "referrer": ReferrerPolicy().no_referrer(),
                "xcto": XContentTypeOptions().nosniff(),
                "xfo": XFrameOptions().sameorigin(),
                "xdfc": XDnsPrefetchControl().disable(),
                "xpcdp": XPermittedCrossDomainPolicies().none(),
                "custom": [
                    CustomHeader(
                        header="Origin-Agent-Cluster",
                        value="?1",
                    ),
                    CustomHeader(
                        header="X-Download-Options",
                        value="noopen",
                    ),
                    CustomHeader(
                        header="X-XSS-Protection",
                        value="0",
                    ),
                ],
            }
        case Preset.BALANCED:
            return {
                "coop": CrossOriginOpenerPolicy().same_origin(),
                "corp": CrossOriginResourcePolicy().same_origin(),
                "csp": _baseline_content_security_policy(),
                "hsts": StrictTransportSecurity().max_age(31536000).include_subdomains(),
                "permissions": PermissionsPolicy().geolocation().microphone().camera(),
                "referrer": ReferrerPolicy().strict_origin_when_cross_origin(),
                "server": Server().set(""),
                "xcto": XContentTypeOptions().nosniff(),
                "xfo": XFrameOptions().sameorigin(),
            }
        case Preset.STRICT:
            return {
                "cache": CacheControl().no_store().max_age(0),
                "coep": CrossOriginEmbedderPolicy().require_corp(),
                "coop": CrossOriginOpenerPolicy().same_origin(),
                "csp": (
                    ContentSecurityPolicy()
                    .default_src("'self'")
                    .script_src("'self'")
                    .style_src("'self'")
                    .object_src("'none'")
                    .base_uri("'none'")
                    .frame_ancestors("'none'")
                ),
                "hsts": StrictTransportSecurity().max_age(63072000).include_subdomains(),
                "permissions": PermissionsPolicy().geolocation().microphone().camera(),
                "referrer": ReferrerPolicy().no_referrer(),
                "server": Server().set(""),
                "xcto": XContentTypeOptions().nosniff(),
                "xfo": XFrameOptions().deny(),
            }
        case _:
            raise ValueError(f"Unknown preset: {preset}")


================================================
FILE: secure/_internal/types.py
================================================
from __future__ import annotations

from typing import Literal, Protocol, TypeAlias

OnInvalidPolicy = Literal["drop", "warn", "raise"]
DeduplicateAction = Literal["raise", "first", "last", "concat"]
OnUnexpectedPolicy = Literal["raise", "drop", "warn"]


class HeadersProtocol(Protocol):
    @property
    def headers(self) -> object: ...


class SetHeaderProtocol(Protocol):
    def set_header(self, key: str, value: str) -> object | None: ...


ResponseProtocol: TypeAlias = HeadersProtocol | SetHeaderProtocol
HeaderPair: TypeAlias = tuple[str, str]
HeaderItems: TypeAlias = tuple[HeaderPair, ...]


================================================
FILE: secure/headers/__init__.py
================================================
from .base_header import BaseHeader
from .cache_control import CacheControl
from .content_security_policy import ContentSecurityPolicy
from .cross_origin_embedder_policy import CrossOriginEmbedderPolicy
from .cross_origin_opener_policy import CrossOriginOpenerPolicy
from .cross_origin_resource_policy import CrossOriginResourcePolicy
from .custom_header import CustomHeader
from .permissions_policy import PermissionsPolicy
from .referrer_policy import ReferrerPolicy
from .server import Server
from .strict_transport_security import StrictTransportSecurity
from .x_content_type_options import XContentTypeOptions
from .x_dns_prefetch_control import XDnsPrefetchControl
from .x_frame_options import XFrameOptions
from .x_permitted_cross_domain_policies import XPermittedCrossDomainPolicies

__all__ = [
    "BaseHeader",
    "CacheControl",
    "ContentSecurityPolicy",
    "CrossOriginEmbedderPolicy",
    "CrossOriginOpenerPolicy",
    "CrossOriginResourcePolicy",
    "CustomHeader",
    "PermissionsPolicy",
    "ReferrerPolicy",
    "Server",
    "StrictTransportSecurity",
    "XContentTypeOptions",
    "XDnsPrefetchControl",
    "XFrameOptions",
    "XPermittedCrossDomainPolicies",
]


================================================
FILE: secure/headers/_validation.py
================================================
from __future__ import annotations


def normalize_header_value(value: str, *, what: str = "header value") -> str:
    """
    Trim whitespace and reject CR/LF characters in header strings.

    Args:
        value: Raw header string.
        what: Description of the value (used for error messages).

    Returns:
        The stripped string without CR/LF characters.

    Raises:
        ValueError: If CR or LF are present.
    """
    if "\r" in value or "\n" in value:
        raise ValueError(f"{what} must not contain CR/LF characters")
    return value.strip()


================================================
FILE: secure/headers/base_header.py
================================================
# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
# https://owasp.org/www-project-secure-headers/#cache-control

from abc import abstractmethod
from dataclasses import dataclass
from enum import Enum


class HeaderName(Enum):
    """Enumeration of standard HTTP security headers.

    This enum provides the header names for various security headers
    like Cache-Control, Content-Security-Policy, Strict-Transport-Security, etc.
    It is used to ensure consistency in header naming across the codebase.
    """

    # Caching
    CACHE_CONTROL = "Cache-Control"

    # Content policy
    CONTENT_SECURITY_POLICY = "Content-Security-Policy"

    # Content policy (report-only)
    CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"

    # Embedding security
    CROSS_ORIGIN_EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy"

    # Context isolation
    CROSS_ORIGIN_OPENER_POLICY = "Cross-Origin-Opener-Policy"

    # Cross-origin resource sharing
    CROSS_ORIGIN_RESOURCE_POLICY = "Cross-Origin-Resource-Policy"

    # Permissions
    PERMISSION_POLICY = "Permissions-Policy"

    # Referrer control
    REFERRER_POLICY = "Referrer-Policy"

    # Server identification
    SERVER = "Server"

    # HTTPS enforcement
    STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"

    # MIME type protection
    X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"

    # DNS prefetching control
    X_DNS_PREFETCH_CONTROL = "X-DNS-Prefetch-Control"

    # Clickjacking protection
    X_FRAME_OPTIONS = "X-Frame-Options"

    # Cross-domain policies
    X_PERMITTED_CROSS_DOMAIN_POLICIES = "X-Permitted-Cross-Domain-Policies"


class HeaderDefaultValue(Enum):
    """Enumeration of default values for standard HTTP security headers.

    This enum provides default values for headers like Cache-Control, Content-Security-Policy,
    Strict-Transport-Security, and others. These values represent recommended security defaults
    where applicable.
    """

    # Cache-Control to prevent caching of sensitive data
    CACHE_CONTROL = "no-store, max-age=0"

    # Basic Content Security Policy to allow resources only from the same origin
    CONTENT_SECURITY_POLICY = (
        "default-src 'self'; "
        "script-src 'self'; "
        "style-src 'self'; "
        "object-src 'none'; "
        "base-uri 'self'; "
        "frame-ancestors 'self'; "
        "form-action 'self'"
    )

    # Cross-Origin Embedder Policy set to 'require-corp' to enforce stricter security.
    # This ensures that embedded cross-origin resources must explicitly allow being embedded.
    # Note: This may break third-party content that does not allow cross-origin embedding.
    CROSS_ORIGIN_EMBEDDER_POLICY = "require-corp"

    # Cross-Origin Opener Policy to isolate browsing contexts and prevent cross-origin leaks
    CROSS_ORIGIN_OPENER_POLICY = "same-origin"

    # Cross-Origin Resource Policy to restrict resource loading to the same origin
    CROSS_ORIGIN_RESOURCE_POLICY = "same-origin"

    # Permissions Policy to disable risky features by default (geolocation, microphone, camera)
    PERMISSION_POLICY = "geolocation=(), microphone=(), camera=()"

    # Referrer Policy to balance security and usability, limits information sent on cross-origin requests
    REFERRER_POLICY = "strict-origin-when-cross-origin"

    # Server header omitted to hide server details from attackers
    SERVER = ""

    # Strict Transport Security to enforce HTTPS for one year
    STRICT_TRANSPORT_SECURITY = "max-age=31536000"

    # Prevent MIME-type sniffing to block potential security threats from improperly typed content
    X_CONTENT_TYPE_OPTIONS = "nosniff"

    # X-DNS-Prefetch-Control to disable DNS prefetching for privacy
    X_DNS_PREFETCH_CONTROL = "off"

    # Clickjacking protection, allows framing only from the same origin
    X_FRAME_OPTIONS = "SAMEORIGIN"

    # X-Permitted-Cross-Domain-Policies to disallow all cross-domain policies
    X_PERMITTED_CROSS_DOMAIN_POLICIES = "none"


@dataclass
class BaseHeader:
    """Abstract base class for HTTP security headers.

    This class defines the basic structure for security headers by requiring
    derived classes to implement the `header_value` property, which provides
    the value of the header.

    Attributes:
        header_name (str): The name of the security header.
    """

    header_name: str

    @property
    @abstractmethod
    def header_value(self) -> str:
        """Abstract property for getting the header value.

        This property should be implemented by subclasses to return the
        security header's value.

        Returns:
            str: The value of the header.
        """
        ...


================================================
FILE: secure/headers/cache_control.py
================================================
# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control
# https://owasp.org/www-project-secure-headers/#cache-control
#
# Cache-Control by Mozilla Contributors is licensed under CC-BY-SA 2.5.
# https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor
# https://creativecommons.org/licenses/by-sa/2.5/

from __future__ import annotations

from dataclasses import dataclass, field
import re
from typing import ClassVar

from secure.headers._validation import normalize_header_value
from secure.headers.base_header import BaseHeader, HeaderDefaultValue, HeaderName

_TOKEN_RE = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")


@dataclass
class CacheControl(BaseHeader):
    """
    Fluent builder for the `Cache-Control` HTTP header.

    Default header value: `no-store, max-age=0`

    Notes:
        * Directive names are case-insensitive; lowercase is the recommended form.
        * Directives are comma-separated and resilient to repeated calls for the
          same helper.
        * Common directives follow a deterministic, canonical order to keep
          serialized output stable regardless of call order.

    Resources:
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control
        - https://owasp.org/www-project-secure-headers/#cache-control
    """

    header_name: str = field(init=False, default=HeaderName.CACHE_CONTROL.value, repr=False)

    # Directive storage:
    # - Keys are lowercase directive names (e.g., "max-age", "no-store")
    # - Values are None (valueless directive) or a string (e.g., "60")
    _directives: dict[str, str | None] = field(default_factory=dict)

    # Extra/unrecognized directives (escape hatch). Stored as fully-rendered tokens.
    _extras: list[str] = field(default_factory=list)

    # Exact override for the entire header value (escape hatch).
    _raw_value: str | None = None

    # Library default value for when no directives are set.
    _default_value: str = HeaderDefaultValue.CACHE_CONTROL.value

    # Deterministic serialization order (independent of call order).
    _CANONICAL_ORDER: ClassVar[tuple[str, ...]] = (
        # Common "secure" baseline and typical patterns first.
        "no-store",
        "no-cache",
        "private",
        "public",
        # Age-based caching controls.
        "max-age",
        "s-maxage",
        "max-stale",
        "min-fresh",
        # Validation / capability controls.
        "must-revalidate",
        "proxy-revalidate",
        "must-understand",
        # Transformation / immutability / stale extensions.
        "no-transform",
        "immutable",
        "stale-while-revalidate",
        "stale-if-error",
        # Request-only.
        "only-if-cached",
    )

    # -------------------------------------------------------------------------
    # Serialization
    # -------------------------------------------------------------------------

    @property
    def header_value(self) -> str:
        """Return the current `Cache-Control` header value, or the default if unset."""
        if self._raw_value is not None:
            return self._raw_value

        if not self._directives and not self._extras:
            return self._default_value

        parts: list[str] = []
        present_names: set[str] = set()

        def _add(name: str, val: str | None) -> None:
            token = name if val is None else f"{name}={val}"
            parts.append(token)
            present_names.add(name)

        for name in self._CANONICAL_ORDER:
            if name in self._directives:
                _add(name, self._directives[name])

        for name in sorted(k for k in self._directives if k not in self._CANONICAL_ORDER):
            _add(name, self._directives[name])

        # Append extras, skipping anything that would duplicate a directive name.
        # Extras are sorted for determinism.
        for token in sorted(set(self._extras)):
            extra_name = token.split("=", 1)[0].strip().lower()
            if not extra_name or extra_name in present_names:
                continue
            parts.append(token)

        return ", ".join(parts)

    # -------------------------------------------------------------------------
    # Escape hatches / resets
    # -------------------------------------------------------------------------

    def value(self, value: str) -> CacheControl:
        """
        Set an explicit header value, replacing all configured directives.

        This is an escape hatch: it bypasses directive helpers.

        Safety:
        - Rejects CR/LF to prevent header-splitting.
        - Strips leading/trailing whitespace and rejects empty results.
        """
        v = normalize_header_value(value, what="Cache-Control value")
        if not v:
            raise ValueError("Cache-Control value must not be empty")

        self._raw_value = v
        self._directives.clear()
        self._extras.clear()
        return self

    # Backwards-compatible alias (older versions used `set()`).
    def set(self, value: str) -> CacheControl:
        """Alias for :meth:`value`."""
        return self.value(value)

    def clear(self) -> CacheControl:
        """Clear all directives and explicit value, returning to the default state."""
        self._raw_value = None
        self._directives.clear()
        self._extras.clear()
        return self

    def custom(self, directive: str) -> CacheControl:
        """
        Add a custom directive token (non-standard / extra).

        This is intended for directives not covered by helper methods.

        Examples:
            .custom("foo")
            .custom("foo=bar")

        Safety:
        - Rejects commas (would break tokenization).
        - Rejects CR/LF (header-splitting).
        - Validates the directive *name* as an RFC token.
        """
        self._ensure_directive_mode()

        d = directive.strip()
        if not d:
            raise ValueError("custom directive must be a non-empty string")
        if ("," in d) or ("\r" in d) or ("\n" in d):
            raise ValueError("custom directive must not contain ',', CR, or LF characters")

        if "=" in d:
            name, rest = d.split("=", 1)
            name = name.strip().lower()
            if not name or not _TOKEN_RE.match(name):
                raise ValueError(f"custom directive name must be a valid token (got {name!r})")
            token = f"{name}={rest.strip()}"
        else:
            name = d.strip().lower()
            if not _TOKEN_RE.match(name):
                raise ValueError(f"custom directive name must be a valid token (got {name!r})")
            token = name

        if token not in self._extras:
            self._extras.append(token)
        return self

    # -------------------------------------------------------------------------
    # Internal helpers
    # -------------------------------------------------------------------------

    def _ensure_directive_mode(self) -> None:
        if self._raw_value is not None:
            self._raw_value = None

    @staticmethod
    def _validate_seconds(seconds: int) -> int:
        if isinstance(seconds, bool) or not isinstance(seconds, int):
            raise TypeError("seconds must be an integer")
        if seconds < 0:
            raise ValueError("seconds must be a non-negative integer")
        return seconds

    def _set_bool(self, name: str) -> None:
        self._ensure_directive_mode()
        self._directives[name] = None

    def _set_seconds(self, name: str, seconds: int) -> None:
        self._ensure_directive_mode()
        n = self._validate_seconds(seconds)
        self._directives[name] = str(n)

    # -------------------------------------------------------------------------
    # Directive helpers
    # -------------------------------------------------------------------------

    def immutable(self) -> CacheControl:
        """Indicate the response will not be updated while it is fresh."""
        self._set_bool("immutable")
        return self

    def max_age(self, seconds: int) -> CacheControl:
        """Set `max-age=N` (freshness lifetime in responses, acceptable age in requests)."""
        self._set_seconds("max-age", seconds)
        return self

    def max_stale(self, seconds: int | None = None) -> CacheControl:
        """Allow reusing a stale response within `seconds`, or any stale age when omitted (request)."""
        self._ensure_directive_mode()
        if seconds is None:
            self._directives["max-stale"] = None
        else:
            self._directives["max-stale"] = str(self._validate_seconds(seconds))
        return self

    def min_fresh(self, seconds: int) -> CacheControl:
        """Require a stored response to remain fresh for at least `seconds` (request)."""
        self._set_seconds("min-fresh", seconds)
        return self

    def must_revalidate(self) -> CacheControl:
        """Require revalidation with the origin server once a stored response becomes stale (response)."""
        self._set_bool("must-revalidate")
        return self

    def must_understand(self) -> CacheControl:
        """Store the response only if the cache understands the caching requirements for its status code."""
        self._set_bool("must-understand")
        return self

    def no_cache(self) -> CacheControl:
        """Allow storing but require validation with the origin server before each reuse."""
        self._set_bool("no-cache")
        return self

    def no_store(self) -> CacheControl:
        """Instruct caches (private or shared) not to store this response."""
        self._set_bool("no-store")
        return self

    def no_transform(self) -> CacheControl:
        """Instruct intermediaries not to transform the request or response content."""
        self._set_bool("no-transform")
        return self

    def only_if_cached(self) -> CacheControl:
        """Request an already-cached response; if none is available, a 504 may be returned (request)."""
        self._set_bool("only-if-cached")
        return self

    def private(self) -> CacheControl:
        """Indicate the response may be stored only in a private cache (e.g., a browser cache)."""
        self._set_bool("private")
        return self

    def proxy_revalidate(self) -> CacheControl:
        """Like `must-revalidate`, but for shared caches only (response)."""
        self._set_bool("proxy-revalidate")
        return self

    def public(self) -> CacheControl:
        """Indicate the response may be stored in a shared cache (response)."""
        self._set_bool("public")
        return self

    def s_maxage(self, seconds: int) -> CacheControl:
        """Set `s-maxage=N` (freshness lifetime in shared caches only)."""
        self._set_seconds("s-maxage", seconds)
        return self

    def s_max_age(self, seconds: int) -> CacheControl:
        """Alias for :meth:`s_maxage`."""
        return self.s_maxage(seconds)

    def stale_if_error(self, seconds: int) -> CacheControl:
        """Allow reusing a stale response for `seconds` when a 500/502/503/504 error is encountered."""
        self._set_seconds("stale-if-error", seconds)
        return self

    def stale_while_revalidate(self, seconds: int) -> CacheControl:
        """Allow reusing a stale response for `seconds` while revalidation happens in the background."""
        self._set_seconds("stale-while-revalidate", seconds)
        return self


================================================
FILE: secure/headers/content_security_policy.py
================================================
# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
# https://owasp.org/www-project-secure-headers/#content-security-policy
#
# Content-Security-Policy by Mozilla Contributors is licensed under CC-BY-SA 2.5.
# https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor
# https://creativecommons.org/licenses/by-sa/2.5/

from __future__ import annotations  # type: ignore

from dataclasses import dataclass, field
import re

from secure.headers._validation import normalize_header_value
from secure.headers.base_header import BaseHeader, HeaderDefaultValue, HeaderName

_DIRECTIVE_NAME_RE = re.compile(r"^[A-Za-z0-9-]+$")
_NONCE_RE = re.compile(r"^[A-Za-z0-9+/_=-]+$")
_ASCII_SPACE = 0x20
_ASCII_DEL = 0x7F


@dataclass
class ContentSecurityPolicy(BaseHeader):
    """
    Fluent builder for the ``Content-Security-Policy`` HTTP response header.

    Default header value:
        `default-src 'self'; script-src 'self'; style-src 'self';
         object-src 'none'; base-uri 'self'; frame-ancestors 'self';
         form-action 'self'`

    Notes:
        * The structured helpers intentionally avoid full CSP validation; use
          ``.value(...)`` when you need to emit an exact policy string.
        * Multiple policies can be sent by instantiating another
          ``ContentSecurityPolicy`` and adding it to ``Secure.headers_list``.
        * MDN describes fallback behavior between directives (e.g., ``default-src``
          acts as a fallback for fetch directives).

    Resources:
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
        - https://owasp.org/www-project-secure-headers/#content-security-policy
    """

    header_name: str = field(init=False, default=HeaderName.CONTENT_SECURITY_POLICY.value, repr=False)

    # Structured directives built via fluent helpers. Each directive appears at most once.
    # Values are stored as tokens (space-separated in serialization). A value of ``None``
    # means the directive is valueless (for example: ``upgrade-insecure-requests``).
    _directives: dict[str, list[str] | None] = field(default_factory=dict, repr=False)

    # Escape hatch: if set, this raw string is used as the header value.
    _raw_value: str | None = field(default=None, repr=False)

    _default_value: str = field(init=False, default=HeaderDefaultValue.CONTENT_SECURITY_POLICY.value, repr=False)

    @property
    def header_value(self) -> str:
        """Return the current `Content-Security-Policy` header value."""
        if self._raw_value is not None:
            return self._raw_value

        if not self._directives:
            return self._default_value

        parts: list[str] = []
        for directive, values in self._directives.items():
            if values:
                parts.append(f"{directive} {' '.join(values)}")
            else:
                parts.append(directive)
        return "; ".join(parts)

    # -------------------------------------------------------------------------
    # Low-level helpers / escape hatches
    # -------------------------------------------------------------------------

    def value(self, value: str) -> ContentSecurityPolicy:
        """Set an exact header value (escape hatch)."""
        self._raw_value = normalize_header_value(value, what="Content-Security-Policy value")
        self._directives.clear()
        return self

    # Backwards-compatible alias.
    def set(self, value: str) -> ContentSecurityPolicy:
        """Alias for :meth:`value`."""
        return self.value(value)

    def clear(self) -> ContentSecurityPolicy:
        """Clear all configured directives and any raw override.

        After calling this, the header value falls back to the library default.
        """
        self._raw_value = None
        self._directives.clear()
        return self

    def report_only(self) -> ContentSecurityPolicy:
        """Use the report-only header name (`Content-Security-Policy-Report-Only`)."""
        self.header_name = HeaderName.CONTENT_SECURITY_POLICY_REPORT_ONLY.value
        return self

    def enforce(self) -> ContentSecurityPolicy:
        """Use the enforcing header name (`Content-Security-Policy`)."""
        self.header_name = HeaderName.CONTENT_SECURITY_POLICY.value
        return self

    def custom(self, directive: str, *values: str) -> ContentSecurityPolicy:
        """Alias for :meth:`custom_directive`."""
        return self.custom_directive(directive, *values)

    def custom_directive(self, directive: str, *values: str) -> ContentSecurityPolicy:
        """Add (or update) a directive.

        - Directives are de-duplicated: each directive name appears at most once.
        - Values are treated as tokens: duplicates are removed (preserving order).
        - Passing no values sets a valueless directive (overwriting prior values).

        Args:
            directive: Directive name (for example, ``default-src``).
            *values: Directive tokens (for example, ``'self'``, ``https:``, ``example.com``).

        Returns:
            The same instance, for method chaining.
        """
        self._touch_structured()
        d = self._normalize_directive_name(directive)

        if not values:
            # Valueless directive (or explicit "clear values" for a directive).
            self._directives[d] = None
            return self

        tokens = [self._validate_token(v) for v in values]

        existing = self._directives.get(d)
        if existing is None:
            existing_list: list[str] = []
            self._directives[d] = existing_list
        else:
            existing_list = existing

        # De-dupe while preserving insertion order.
        seen = set(existing_list)
        for t in tokens:
            if t not in seen:
                existing_list.append(t)
                seen.add(t)
        return self

    # -------------------------------------------------------------------------
    # Directive helpers (alphabetical by directive name)
    # -------------------------------------------------------------------------

    def base_uri(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for the document `<base>` element."""
        return self.custom_directive("base-uri", *sources)

    def block_all_mixed_content(self) -> ContentSecurityPolicy:
        """Prevent loading any assets using HTTP when the page is loaded using HTTPS.

        Deprecated in MDN's reference; prefer modern HTTPS-only deployments and
        consider `upgrade-insecure-requests` instead when appropriate.
        """
        return self.custom_directive("block-all-mixed-content")

    def child_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for web workers and nested browsing contexts."""
        return self.custom_directive("child-src", *sources)

    def connect_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for script interfaces (for example, XHR, Fetch, WebSocket)."""
        return self.custom_directive("connect-src", *sources)

    def default_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set the fallback policy for all fetch directives."""
        return self.custom_directive("default-src", *sources)

    def fenced_frame_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for nested browsing contexts loaded into `<fencedframe>`."""
        return self.custom_directive("fenced-frame-src", *sources)

    def font_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for fonts."""
        return self.custom_directive("font-src", *sources)

    def form_action(self, *sources: str) -> ContentSecurityPolicy:
        """Restrict the URLs which can be used as the target of form submissions."""
        return self.custom_directive("form-action", *sources)

    def frame_ancestors(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid parent sources that may embed the page in a frame."""
        return self.custom_directive("frame-ancestors", *sources)

    def frame_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for nested browsing contexts loaded into frames/iframes."""
        return self.custom_directive("frame-src", *sources)

    def img_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for images and favicons."""
        return self.custom_directive("img-src", *sources)

    def manifest_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for application manifests."""
        return self.custom_directive("manifest-src", *sources)

    def media_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for media (audio, video, track)."""
        return self.custom_directive("media-src", *sources)

    def object_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for plugin-like objects (for example, `<object>`, `<embed>`)."""
        return self.custom_directive("object-src", *sources)

    def prefetch_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources to be prefetched or prerendered.

        Deprecated and non-standard in MDN's reference; use only if you have a
        specific compatibility need.
        """
        return self.custom_directive("prefetch-src", *sources)

    def report_to(self, *values: str) -> ContentSecurityPolicy:
        """Configure reporting endpoints via `report-to` groups."""
        return self.custom_directive("report-to", *values)

    def report_uri(self, *uris: str) -> ContentSecurityPolicy:
        """Configure the legacy reporting endpoint(s) via `report-uri`.

        Deprecated in MDN's reference. If you use `report-to`, note that browsers
        that support `report-to` ignore `report-uri`.
        """
        return self.custom_directive("report-uri", *uris)

    def require_trusted_types_for(self, *values: str) -> ContentSecurityPolicy:
        """Enforce Trusted Types at specific DOM injection sinks."""
        return self.custom_directive("require-trusted-types-for", *values)

    def sandbox(self, *values: str) -> ContentSecurityPolicy:
        """Enable a sandbox for the requested resource (similar to `<iframe sandbox>`)."""
        return self.custom_directive("sandbox", *values)

    def script_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for JavaScript and WebAssembly resources."""
        return self.custom_directive("script-src", *sources)

    def script_src_attr(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for inline event handlers."""
        return self.custom_directive("script-src-attr", *sources)

    def script_src_elem(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for `<script>` elements."""
        return self.custom_directive("script-src-elem", *sources)

    def style_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for stylesheets."""
        return self.custom_directive("style-src", *sources)

    def style_src_attr(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for inline styles on individual elements."""
        return self.custom_directive("style-src-attr", *sources)

    def style_src_elem(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for `<style>` and stylesheet `<link>` elements."""
        return self.custom_directive("style-src-elem", *sources)

    def trusted_types(self, *values: str) -> ContentSecurityPolicy:
        """Specify an allowlist of Trusted Types policies."""
        return self.custom_directive("trusted-types", *values)

    def upgrade_insecure_requests(self) -> ContentSecurityPolicy:
        """Upgrade insecure HTTP requests to HTTPS."""
        return self.custom_directive("upgrade-insecure-requests")

    def worker_src(self, *sources: str) -> ContentSecurityPolicy:
        """Set valid sources for `Worker`, `SharedWorker`, and `ServiceWorker` scripts."""
        return self.custom_directive("worker-src", *sources)

    # -------------------------------------------------------------------------
    # CSP value helpers
    # -------------------------------------------------------------------------

    @staticmethod
    def keyword(name: str) -> str:
        """Return a quoted CSP keyword/source expression (for example, ``'self'``)."""
        if not name:
            raise ValueError("CSP keyword must be non-empty")
        if any(ch.isspace() for ch in name) or any(ch in name for ch in "'\";\r\n"):
            raise ValueError("CSP keyword contains invalid characters")
        return f"'{name}'"

    @staticmethod
    def nonce(value: str) -> str:
        """Create a nonce source expression for inline scripts or styles.

        The provided value should be Base64 or URL-safe Base64.
        """
        if not value or not _NONCE_RE.fullmatch(value):
            raise ValueError("nonce value must be Base64 (or URL-safe Base64) characters only")
        return f"'nonce-{value}'"

    # -------------------------------------------------------------------------
    # Internal helpers
    # -------------------------------------------------------------------------

    def _touch_structured(self) -> None:
        """Switch from raw override to structured directive building (if needed)."""
        if self._raw_value is not None:
            self._raw_value = None

    @staticmethod
    def _normalize_directive_name(directive: str) -> str:
        if not directive:
            raise ValueError("directive name must be non-empty")
        if any(ch.isspace() for ch in directive) or any(ch in directive for ch in ";\r\n"):
            raise ValueError("directive name contains invalid characters")
        if not _DIRECTIVE_NAME_RE.fullmatch(directive):
            raise ValueError(f"invalid directive name: {directive!r}")
        return directive

    @staticmethod
    def _validate_token(token: str) -> str:
        if not token:
            raise ValueError("directive value tokens must be non-empty")
        if any(ch.isspace() for ch in token) or any(ch in token for ch in ";\r\n"):
            raise ValueError(f"directive token contains invalid characters: {token!r}")
        # Disallow other ASCII control characters.
        if any(ord(ch) < _ASCII_SPACE or ord(ch) == _ASCII_DEL for ch in token):
            raise ValueError(f"directive token contains control characters: {token!r}")
        return token


================================================
FILE: secure/headers/cross_origin_embedder_policy.py
================================================
# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy
# https://owasp.org/www-project-secure-headers/#cross-origin-embedder-policy
#
# Cross-Origin-Embedder-Policy by Mozilla Contributors is licensed under CC-BY-SA 2.5.
# https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor
# https://creativecommons.org/licenses/by-sa/2.5/

from __future__ import annotations  # type: ignore

from dataclasses import dataclass, field
from typing import Literal

from secure.headers._validation import normalize_header_value
from secure.headers.base_header import BaseHeader, HeaderDefaultValue, HeaderName

COEPDirective = Literal["unsafe-none", "require-corp", "credentialless"]


@dataclass
class CrossOriginEmbedderPolicy(BaseHeader):
    """
    Builder for the ``Cross-Origin-Embedder-Policy`` (COEP) HTTP response header.

    COEP controls how the document embeds and loads cross-origin resources, with
    directives that range from no isolation (``unsafe-none``) to strict isolation
    (``require-corp``) or credentialless loading.

    Default header value: ``require-corp``

    Notes:
        * Per MDN, omitting the header is equivalent to ``unsafe-none``.
        * Each helper closes over canonical MDN directives while ``value(...)``
          acts as an escape hatch for custom strings.

    Resources:
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy
        - https://owasp.org/www-project-secure-headers/#cross-origin-embedder-policy
    """

    header_name: str = field(init=False, default=HeaderName.CROSS_ORIGIN_EMBEDDER_POLICY.value, repr=False)
    _default_value: str = field(init=False, default=HeaderDefaultValue.CROSS_ORIGIN_EMBEDDER_POLICY.value, repr=False)
    _directive: str = field(default=HeaderDefaultValue.CROSS_ORIGIN_EMBEDDER_POLICY.value, repr=False)

    def _normalize(self, value: str) -> str:
        """Normalize a directive value (trim + lowercase)."""
        v = normalize_header_value(value, what="Cross-Origin-Embedder-Policy value")

        if not v:
            return HeaderDefaultValue.CROSS_ORIGIN_EMBEDDER_POLICY.value
        return v.lower()

    @property
    def header_value(self) -> str:
        """Return the current ``Cross-Origin-Embedder-Policy`` header value."""
        return self._normalize(self._directive)

    def set(self, value: COEPDirective | str) -> CrossOriginEmbedderPolicy:
        """Set a COEP directive.

        This method accepts any string as an escape hatch. For MDN-defined values,
        prefer :meth:`unsafe_none`, :meth:`require_corp`, or :meth:`credentialless`.

        Args:
            value: Directive value (e.g., ``"require-corp"``).

        Returns:
            This instance for method chaining.
        """
        self._directive = self._normalize(str(value))
        return self

    def value(self, value: COEPDirective | str) -> CrossOriginEmbedderPolicy:
        """Alias for :meth:`set` to align with other headers."""
        return self.set(value)

    def clear(self) -> CrossOriginEmbedderPolicy:
        """Reset to the library default directive."""
        self._directive = self._default_value
        return self

    def unsafe_none(self) -> CrossOriginEmbedderPolicy:
        """Set COEP to ``unsafe-none``.

        ``unsafe-none`` allows the document to load cross-origin resources without
        explicit CORP/CORS permission.
        """
        self._directive = "unsafe-none"
        return self

    def require_corp(self) -> CrossOriginEmbedderPolicy:
        """Set COEP to ``require-corp``.

        ``require-corp`` blocks cross-origin resource loading unless the resource
        is explicitly permitted via CORP (for ``no-cors``) or via CORS (for ``cors``).
        """
        self._directive = "require-corp"
        return self

    def credentialless(self) -> CrossOriginEmbedderPolicy:
        """Set COEP to ``credentialless``.

        ``credentialless`` allows loading some cross-origin resources without
        explicit CORP opt-in, but strips credentials (cookies are omitted on the
        request and ignored in the response).
        """
        self._directive = "credentialless"
        return self


================================================
FILE: secure/headers/cross_origin_opener_policy.py
================================================
# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy
# https://owasp.org/www-project-secure-headers/#cross-origin-opener-policy
#
# Cross-Origin-Opener-Policy by Mozilla Contributors is licensed under CC-BY-SA 2.5.
# https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor
# https://creativecommons.org/licenses/by-sa/2.5/

from __future__ import annotations  # type: ignore

from dataclasses import dataclass, field
from typing import Final, Literal

from secure.headers._validation import normalize_header_value
from secure.headers.base_header import BaseHeader, HeaderDefaultValue, HeaderName

COOPDirective = Literal[
    "unsafe-none",
    "same-origin-allow-popups",
    "same-origin",
    "noopener-allow-popups",
]

# Library default (secure-header libs often default to isolation).
# Note: per MDN/spec behavior, if the header is absent, the effective default is "unsafe-none".
DEFAULT_VALUE: Final[str] = HeaderDefaultValue.CROSS_ORIGIN_OPENER_POLICY.value


@dataclass
class CrossOriginOpenerPolicy(BaseHeader):
    """
    Builder for the ``Cross-Origin-Opener-Policy`` (COOP) HTTP response header.

    COOP lets a page opt into a dedicated browsing context group or share like with
    its opener, helping protect against XS-Leaks.

    Default header value: ``same-origin``

    Notes:
        * If this header is absent, browsers behave as if ``unsafe-none`` were set.
        * Use the fluent helpers to pick MDN-defined directives; ``value(...)`` is
          provided as an escape hatch.

    Resources:
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy
        - https://owasp.org/www-project-secure-headers/#cross-origin-opener-policy
    """

    header_name: str = field(init=False, default=HeaderName.CROSS_ORIGIN_OPENER_POLICY.value, repr=False)
    _default_value: str = field(init=False, default=DEFAULT_VALUE, repr=False)
    _directive: str = field(default=DEFAULT_VALUE, repr=False)

    @property
    def header_value(self) -> str:
        """Return the current `Cross-Origin-Opener-Policy` header value."""
        return self._directive

    # ---------------------------------------------------------------------
    # Escape hatches
    # ---------------------------------------------------------------------

    def value(self, directive: str) -> CrossOriginOpenerPolicy:
        """
        Set a custom value for the `Cross-Origin-Opener-Policy` header.

        This is an escape hatch. Prefer the explicit directive helpers when possible.

        Safety:
            Rejects CR/LF to avoid header-splitting. Additional validation (obs-text, etc.)
            remains the responsibility of `Secure.validate_and_normalize_headers(...)`.

        Args:
            directive: Custom header value (usually one of the COOP directives).

        Returns:
            The `CrossOriginOpenerPolicy` instance for method chaining.
        """
        v = normalize_header_value(directive, what="Cross-Origin-Opener-Policy value")
        self._directive = v
        return self

    def custom(self, directive: str) -> CrossOriginOpenerPolicy:
        """Alias for :meth:`value`."""
        return self.value(directive)

    def set(self, value: str) -> CrossOriginOpenerPolicy:
        """
        Backwards-compatible alias for :meth:`value`.

        Prefer :meth:`value` or :meth:`custom` in v2+ for consistency.
        """
        return self.value(value)

    def clear(self) -> CrossOriginOpenerPolicy:
        """
        Reset the `Cross-Origin-Opener-Policy` header to the library default value.

        Returns:
            The `CrossOriginOpenerPolicy` instance for method chaining.
        """
        self._directive = self._default_value
        return self

    # ---------------------------------------------------------------------
    # Directive helpers (fluent API)
    # ---------------------------------------------------------------------

    def unsafe_none(self) -> CrossOriginOpenerPolicy:
        """
        Set the header to `'unsafe-none'`.

        This opts out of COOP-based isolation.

        Returns:
            The `CrossOriginOpenerPolicy` instance for method chaining.
        """
        self._directive = "unsafe-none"
        return self

    def same_origin_allow_popups(self) -> CrossOriginOpenerPolicy:
        """
        Set the header to `'same-origin-allow-popups'`.

        Similar to `same-origin`, but allows opening documents with COOP `unsafe-none`
        in the same browsing context group for `Window.open()` integrations.

        Returns:
            The `CrossOriginOpenerPolicy` instance for method chaining.
        """
        self._directive = "same-origin-allow-popups"
        return self

    def same_origin(self) -> CrossOriginOpenerPolicy:
        """
        Set the header to `'same-origin'`.

        Restricts browsing context group sharing to same-origin documents that also
        use `same-origin`. Commonly used as part of cross-origin isolation.

        Returns:
            The `CrossOriginOpenerPolicy` instance for method chaining.
        """
        self._directive = "same-origin"
        return self

    def noopener_allow_popups(self) -> CrossOriginOpenerPolicy:
        """
        Set the header to `'noopener-allow-popups'`.

        This severs opener relationships while still allowing popups, and is used to
        isolate documents even from same-origin openers in some workflows.

        Returns:
            The `CrossOriginOpenerPolicy` instance for method chaining.
        """
        self._directive = "noopener-allow-popups"
        return self


================================================
FILE: secure/headers/cross_origin_resource_policy.py
================================================
# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Resource-Policy
# https://owasp.org/www-project-secure-headers/#cross-origin-resource-policy
#
# Cross-Origin-Resource-Policy by Mozilla Contributors is licensed under CC-BY-SA 2.5.
# https://developer.mozilla.org/en-US/docs/MDN/Community/Roles_teams#contributor
# https://creativecommons.org/licenses/by-sa/2.5/

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Final, Literal

from secure.headers._validation import normalize_header_value
from secure.headers.base_header import BaseHeader, HeaderDefaultValue, HeaderName

CorpDirective = Literal["same-site", "same-origin", "cross-origin"]

_ALLOWED: Final[frozenset[str]] = frozenset({"same-site", "same-origin", "cross-origin"})


@dataclass
class CrossOriginResourcePolicy(BaseHeader):
    """
    Builder for the ``Cross-Origin-Resource-Policy`` (CORP) HTTP response header.

    CORP expresses the resource owner's intent for which origins may load this
    resource, with MDN documenting ``same-site``, ``same-origin``, and
    ``cross-origin`` directives.

    Default header value: `same-origin`

    Resources:
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Resource-Policy
        - https://resourcepolicy.fyi/
        - https://owasp.org/www-project-secure-headers/#cross-origin-resource-policy
    """

    header_name: str = field(init=False, default=HeaderName.CROSS_ORIGIN_RESOURCE_POLICY.value, repr=False)
    _default_value: str = field(init=False, default=HeaderDefaultValue.CROSS_ORIGIN_RESOURCE_POLICY.value, repr=False)
    _value: str = field(default_factory=lambda: HeaderDefaultValue.CROSS_ORIGIN_RESOURCE_POLICY.value, repr=False)

    @property
    def header_value(self) -> str:
        """Return the current header value."""
        return self._value

    def clear(self) -> CrossOriginResourcePolicy:
        """
        Reset this header to the library default value.

        Returns:
            The `CrossOriginResourcePolicy` instance for method chaining.
        """
        self._value = self._default_value
        return self

    def value(self, value: str | CorpDirective) -> CrossOriginResourcePolicy:
        """
        Set the header value.

        This is the preferred "escape hatch" API. For known CORP directives, the
        stored value is canonicalized to the standard lowercase token.

        Args:
            value:
                Typically one of `same-origin`, `same-site`, or `cross-origin`.
                Other values are accepted as-is (after trimming), but are not
                described by MDN.

        Returns:
            The `CrossOriginResourcePolicy` instance for method chaining.

        Raises:
            ValueError: if the value contains CR/LF characters.
        """
        self._value = self._normalize_value(str(value))
        return self

    # Backwards-compatible alias (keep for existing callers).
    def set(self, value: str) -> CrossOriginResourcePolicy:
        """
        Backwards-compatible alias for `value(...)`.

        Prefer `value(...)` going forward.
        """
        return self.value(value)

    def same_origin(self) -> CrossOriginResourcePolicy:
        """Restrict resource loading to the same origin."""
        self._value = "same-origin"
        return self

    def same_site(self) -> CrossOriginResourcePolicy:
        """Allow resource loading from the same site."""
        self._value = "same-site"
        return self

    def cross_origin(self) -> CrossOriginResourcePolicy:
        """Allow resource loading from any origin."""
        self._value = "cross-origin"
        return self

    @staticmethod
    def _normalize_value(value: str) -> str:
        v = normalize_header_value(value, what="Cross-Origin-Resource-Policy value")

        # Canonicalize known directives (case-insensitive) to the MDN tokens.
        lc = v.lower()
        if lc in _ALLOWED:
            return lc

        # Unknown: keep trimmed string verbatim as an escape hatch.
        return v


================================================
FILE: secure/headers/custom_header.py
================================================
from __future__ import annotations  # type: ignore

from dataclasses import dataclass, field

from secure.headers._validation import normalize_header_value
from secure.headers.base_header import BaseHeader


@dataclass
class CustomHeader(BaseHeader):
    """
    Wrapper for an arbitrary HTTP header.

    Default header value: provided by the caller at initialization.

    Notes:
        * Header names and values are normalized via ``normalize_header_value`` to
          prevent header injection.
        * This class keeps parity with other builders via ``value``, ``set``, and
          escape-hatch helpers so it plugs into the fluent API.

    Resources:
        - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
    """

    header_name: str
    _value: str = field(repr=False)

    def __init__(self, header: str, value: str) -> None:
        """
        Initialize a custom header name and value.

        Args:
            header: The header name (for example, ``"X-Custom-Header"``).
            value: The header value to emit.
        """
        self.header_name = normalize_header_value(header, what="custom header name")
        self._value = value

    @property
    def header_value(self) -> str:
        """
        Retrieve the current value of the custom header.

        Returns:
            str: The value of the custom header.
        """
        return self._value

    def set(self, value: str) -> CustomHeader:
        """
        Update the value of the custom header.

        This method allows the value of the custom header to be updated
    
Download .txt
gitextract___lgw2bt/

├── .github/
│   └── workflows/
│       └── tests.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│   ├── README.md
│   ├── configuration.md
│   ├── frameworks.md
│   ├── headers/
│   │   ├── cache_control.md
│   │   ├── content_security_policy.md
│   │   ├── cross-origin-resource-policy.md
│   │   ├── cross_origin_embedder_policy.md
│   │   ├── cross_origin_opener_policy.md
│   │   ├── custom_header.md
│   │   ├── dns_prefetch_control.md
│   │   ├── permissions_policy.md
│   │   ├── referrer_policy.md
│   │   ├── server.md
│   │   ├── strict_transport_security.md
│   │   ├── x-permitted-cross-domain-policies.md
│   │   ├── x_content_type_options.md
│   │   └── x_frame_options.md
│   ├── installation.md
│   ├── migration.md
│   ├── security_considerations.md
│   └── usage.md
├── pyproject.toml
├── secure/
│   ├── __init__.py
│   ├── _internal/
│   │   ├── __init__.py
│   │   ├── configured_headers.py
│   │   ├── constants.py
│   │   ├── emit.py
│   │   ├── normalize.py
│   │   ├── policy.py
│   │   ├── presets.py
│   │   └── types.py
│   ├── headers/
│   │   ├── __init__.py
│   │   ├── _validation.py
│   │   ├── base_header.py
│   │   ├── cache_control.py
│   │   ├── content_security_policy.py
│   │   ├── cross_origin_embedder_policy.py
│   │   ├── cross_origin_opener_policy.py
│   │   ├── cross_origin_resource_policy.py
│   │   ├── custom_header.py
│   │   ├── permissions_policy.py
│   │   ├── referrer_policy.py
│   │   ├── server.py
│   │   ├── strict_transport_security.py
│   │   ├── x_content_type_options.py
│   │   ├── x_dns_prefetch_control.py
│   │   ├── x_frame_options.py
│   │   └── x_permitted_cross_domain_policies.py
│   ├── middleware/
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   └── wsgi.py
│   ├── py.typed
│   └── secure.py
└── tests/
    ├── __init__.py
    ├── headers/
    │   ├── test_cache_control.py
    │   ├── test_content_security_policy.py
    │   ├── test_cross_origin_embedder_policy.py
    │   ├── test_cross_origin_opener_policy.py
    │   ├── test_custom_header.py
    │   ├── test_header_contracts_extended.py
    │   ├── test_permissions_policy.py
    │   ├── test_referrer_policy.py
    │   ├── test_server.py
    │   ├── test_strict_transport_security.py
    │   ├── test_x_content_type_options.py
    │   └── test_x_frame_options.py
    ├── middleware/
    │   ├── test_middleware.py
    │   └── test_secure_protocols.py
    └── secure_tests/
        ├── __init__.py
        ├── test_exports.py
        ├── test_headers.py
        ├── test_internal_helpers.py
        └── test_secure.py
Download .txt
SYMBOL INDEX (510 symbols across 43 files)

FILE: secure/_internal/configured_headers.py
  class ConfiguredHeaders (line 17) | class ConfiguredHeaders(list[BaseHeader]):
    method __init__ (line 20) | def __init__(
    method replace_all (line 29) | def replace_all(self, headers: Iterable[HeaderInput]) -> None:
    method append (line 35) | def append(self, header: HeaderInput) -> None:
    method extend (line 39) | def extend(self, headers: Iterable[HeaderInput]) -> None:
    method insert (line 43) | def insert(self, index: int, header: HeaderInput) -> None:
    method __setitem__ (line 48) | def __setitem__(self, index: int, header: HeaderInput) -> None: ...
    method __setitem__ (line 51) | def __setitem__(self, index: slice, header: Iterable[HeaderInput]) -> ...
    method __setitem__ (line 53) | def __setitem__(self, index: int | slice, header: HeaderInput | Iterab...
    method __delitem__ (line 63) | def __delitem__(self, index: int | slice) -> None:
    method clear (line 67) | def clear(self) -> None:
    method pop (line 71) | def pop(self, index: int = -1) -> BaseHeader:
    method remove (line 76) | def remove(self, header: BaseHeader) -> None:
    method reverse (line 80) | def reverse(self) -> None:
    method sort (line 84) | def sort(self, *, key: Callable[[BaseHeader], Any] | None = None, reve...
    method __iadd__ (line 88) | def __iadd__(self, headers: Iterable[HeaderInput]) -> ConfiguredHeaders:
  function header_items_from_objects (line 93) | def header_items_from_objects(headers: Iterable[BaseHeader]) -> HeaderIt...
  function header_mapping_from_items (line 97) | def header_mapping_from_items(items: HeaderItems) -> MappingProxyType[st...
  function _coerce_header_object (line 111) | def _coerce_header_object(header: HeaderInput, *, operation: str) -> Bas...

FILE: secure/_internal/emit.py
  class HeaderSetError (line 12) | class HeaderSetError(RuntimeError):
  function set_headers_sync (line 16) | def set_headers_sync(response: ResponseProtocol, items: HeaderItems) -> ...
  function set_headers_async (line 36) | async def set_headers_async(response: ResponseProtocol, items: HeaderIte...
  function _get_headers_container (line 47) | def _get_headers_container(response: ResponseProtocol) -> object:
  function _set_headers_container_sync (line 53) | def _set_headers_container_sync(headers: object, items: HeaderItems) -> ...
  function _set_headers_container_async (line 71) | async def _set_headers_container_async(headers: object, items: HeaderIte...
  function _apply_sync_setter (line 80) | def _apply_sync_setter(
  function _apply_async_setter (line 101) | async def _apply_async_setter(setter: object, items: HeaderItems) -> None:
  function _set_headers_mapping_sync (line 113) | def _set_headers_mapping_sync(headers: object, items: HeaderItems) -> None:
  function _set_headers_mapping_async (line 131) | async def _set_headers_mapping_async(headers: object, items: HeaderItems...

FILE: secure/_internal/normalize.py
  class _NormalizationOptions (line 23) | class _NormalizationOptions:
  function normalize_header_items (line 30) | def normalize_header_items(
  function _validate_header_pair (line 74) | def _validate_header_pair(
  function _handle_invalid (line 129) | def _handle_invalid(message: str, *, options: _NormalizationOptions) -> ...
  function _value_is_allowed (line 136) | def _value_is_allowed(value: str, *, allow_obs_text: bool) -> bool:
  function _is_allowed_value_char (line 140) | def _is_allowed_value_char(char: str, *, allow_obs_text: bool) -> bool:
  function _sanitize_value (line 150) | def _sanitize_value(name: str, value: str, *, strict: bool, allow_obs_te...

FILE: secure/_internal/policy.py
  function deduplicate_header_objects (line 16) | def deduplicate_header_objects(
  function allowlist_header_objects (line 70) | def allowlist_header_objects(  # noqa: PLR0913
  function _require_header_object (line 120) | def _require_header_object(header: object, *, operation: str) -> BaseHea...
  function _clone_as_custom_header (line 126) | def _clone_as_custom_header(name: str, value: str) -> BaseHeader:
  function _resolve_duplicate_headers (line 130) | def _resolve_duplicate_headers(
  function _is_allowed_header_name (line 161) | def _is_allowed_header_name(

FILE: secure/_internal/presets.py
  class Preset (line 23) | class Preset(Enum):
  function _baseline_content_security_policy (line 31) | def _baseline_content_security_policy() -> ContentSecurityPolicy:
  function preset_kwargs (line 49) | def preset_kwargs(preset: Preset) -> dict[str, object]:

FILE: secure/_internal/types.py
  class HeadersProtocol (line 10) | class HeadersProtocol(Protocol):
    method headers (line 12) | def headers(self) -> object: ...
  class SetHeaderProtocol (line 15) | class SetHeaderProtocol(Protocol):
    method set_header (line 16) | def set_header(self, key: str, value: str) -> object | None: ...

FILE: secure/headers/_validation.py
  function normalize_header_value (line 4) | def normalize_header_value(value: str, *, what: str = "header value") ->...

FILE: secure/headers/base_header.py
  class HeaderName (line 10) | class HeaderName(Enum):
  class HeaderDefaultValue (line 61) | class HeaderDefaultValue(Enum):
  class BaseHeader (line 120) | class BaseHeader:
    method header_value (line 135) | def header_value(self) -> str:

FILE: secure/headers/cache_control.py
  class CacheControl (line 22) | class CacheControl(BaseHeader):
    method header_value (line 86) | def header_value(self) -> str:
    method value (line 123) | def value(self, value: str) -> CacheControl:
    method set (line 143) | def set(self, value: str) -> CacheControl:
    method clear (line 147) | def clear(self) -> CacheControl:
    method custom (line 154) | def custom(self, directive: str) -> CacheControl:
    method _ensure_directive_mode (line 197) | def _ensure_directive_mode(self) -> None:
    method _validate_seconds (line 202) | def _validate_seconds(seconds: int) -> int:
    method _set_bool (line 209) | def _set_bool(self, name: str) -> None:
    method _set_seconds (line 213) | def _set_seconds(self, name: str, seconds: int) -> None:
    method immutable (line 222) | def immutable(self) -> CacheControl:
    method max_age (line 227) | def max_age(self, seconds: int) -> CacheControl:
    method max_stale (line 232) | def max_stale(self, seconds: int | None = None) -> CacheControl:
    method min_fresh (line 241) | def min_fresh(self, seconds: int) -> CacheControl:
    method must_revalidate (line 246) | def must_revalidate(self) -> CacheControl:
    method must_understand (line 251) | def must_understand(self) -> CacheControl:
    method no_cache (line 256) | def no_cache(self) -> CacheControl:
    method no_store (line 261) | def no_store(self) -> CacheControl:
    method no_transform (line 266) | def no_transform(self) -> CacheControl:
    method only_if_cached (line 271) | def only_if_cached(self) -> CacheControl:
    method private (line 276) | def private(self) -> CacheControl:
    method proxy_revalidate (line 281) | def proxy_revalidate(self) -> CacheControl:
    method public (line 286) | def public(self) -> CacheControl:
    method s_maxage (line 291) | def s_maxage(self, seconds: int) -> CacheControl:
    method s_max_age (line 296) | def s_max_age(self, seconds: int) -> CacheControl:
    method stale_if_error (line 300) | def stale_if_error(self, seconds: int) -> CacheControl:
    method stale_while_revalidate (line 305) | def stale_while_revalidate(self, seconds: int) -> CacheControl:

FILE: secure/headers/content_security_policy.py
  class ContentSecurityPolicy (line 24) | class ContentSecurityPolicy(BaseHeader):
    method header_value (line 60) | def header_value(self) -> str:
    method value (line 80) | def value(self, value: str) -> ContentSecurityPolicy:
    method set (line 87) | def set(self, value: str) -> ContentSecurityPolicy:
    method clear (line 91) | def clear(self) -> ContentSecurityPolicy:
    method report_only (line 100) | def report_only(self) -> ContentSecurityPolicy:
    method enforce (line 105) | def enforce(self) -> ContentSecurityPolicy:
    method custom (line 110) | def custom(self, directive: str, *values: str) -> ContentSecurityPolicy:
    method custom_directive (line 114) | def custom_directive(self, directive: str, *values: str) -> ContentSec...
    method base_uri (line 157) | def base_uri(self, *sources: str) -> ContentSecurityPolicy:
    method block_all_mixed_content (line 161) | def block_all_mixed_content(self) -> ContentSecurityPolicy:
    method child_src (line 169) | def child_src(self, *sources: str) -> ContentSecurityPolicy:
    method connect_src (line 173) | def connect_src(self, *sources: str) -> ContentSecurityPolicy:
    method default_src (line 177) | def default_src(self, *sources: str) -> ContentSecurityPolicy:
    method fenced_frame_src (line 181) | def fenced_frame_src(self, *sources: str) -> ContentSecurityPolicy:
    method font_src (line 185) | def font_src(self, *sources: str) -> ContentSecurityPolicy:
    method form_action (line 189) | def form_action(self, *sources: str) -> ContentSecurityPolicy:
    method frame_ancestors (line 193) | def frame_ancestors(self, *sources: str) -> ContentSecurityPolicy:
    method frame_src (line 197) | def frame_src(self, *sources: str) -> ContentSecurityPolicy:
    method img_src (line 201) | def img_src(self, *sources: str) -> ContentSecurityPolicy:
    method manifest_src (line 205) | def manifest_src(self, *sources: str) -> ContentSecurityPolicy:
    method media_src (line 209) | def media_src(self, *sources: str) -> ContentSecurityPolicy:
    method object_src (line 213) | def object_src(self, *sources: str) -> ContentSecurityPolicy:
    method prefetch_src (line 217) | def prefetch_src(self, *sources: str) -> ContentSecurityPolicy:
    method report_to (line 225) | def report_to(self, *values: str) -> ContentSecurityPolicy:
    method report_uri (line 229) | def report_uri(self, *uris: str) -> ContentSecurityPolicy:
    method require_trusted_types_for (line 237) | def require_trusted_types_for(self, *values: str) -> ContentSecurityPo...
    method sandbox (line 241) | def sandbox(self, *values: str) -> ContentSecurityPolicy:
    method script_src (line 245) | def script_src(self, *sources: str) -> ContentSecurityPolicy:
    method script_src_attr (line 249) | def script_src_attr(self, *sources: str) -> ContentSecurityPolicy:
    method script_src_elem (line 253) | def script_src_elem(self, *sources: str) -> ContentSecurityPolicy:
    method style_src (line 257) | def style_src(self, *sources: str) -> ContentSecurityPolicy:
    method style_src_attr (line 261) | def style_src_attr(self, *sources: str) -> ContentSecurityPolicy:
    method style_src_elem (line 265) | def style_src_elem(self, *sources: str) -> ContentSecurityPolicy:
    method trusted_types (line 269) | def trusted_types(self, *values: str) -> ContentSecurityPolicy:
    method upgrade_insecure_requests (line 273) | def upgrade_insecure_requests(self) -> ContentSecurityPolicy:
    method worker_src (line 277) | def worker_src(self, *sources: str) -> ContentSecurityPolicy:
    method keyword (line 286) | def keyword(name: str) -> str:
    method nonce (line 295) | def nonce(value: str) -> str:
    method _touch_structured (line 308) | def _touch_structured(self) -> None:
    method _normalize_directive_name (line 314) | def _normalize_directive_name(directive: str) -> str:
    method _validate_token (line 324) | def _validate_token(token: str) -> str:

FILE: secure/headers/cross_origin_embedder_policy.py
  class CrossOriginEmbedderPolicy (line 21) | class CrossOriginEmbedderPolicy(BaseHeader):
    method _normalize (line 45) | def _normalize(self, value: str) -> str:
    method header_value (line 54) | def header_value(self) -> str:
    method set (line 58) | def set(self, value: COEPDirective | str) -> CrossOriginEmbedderPolicy:
    method value (line 73) | def value(self, value: COEPDirective | str) -> CrossOriginEmbedderPolicy:
    method clear (line 77) | def clear(self) -> CrossOriginEmbedderPolicy:
    method unsafe_none (line 82) | def unsafe_none(self) -> CrossOriginEmbedderPolicy:
    method require_corp (line 91) | def require_corp(self) -> CrossOriginEmbedderPolicy:
    method credentialless (line 100) | def credentialless(self) -> CrossOriginEmbedderPolicy:

FILE: secure/headers/cross_origin_opener_policy.py
  class CrossOriginOpenerPolicy (line 30) | class CrossOriginOpenerPolicy(BaseHeader):
    method header_value (line 54) | def header_value(self) -> str:
    method value (line 62) | def value(self, directive: str) -> CrossOriginOpenerPolicy:
    method custom (line 82) | def custom(self, directive: str) -> CrossOriginOpenerPolicy:
    method set (line 86) | def set(self, value: str) -> CrossOriginOpenerPolicy:
    method clear (line 94) | def clear(self) -> CrossOriginOpenerPolicy:
    method unsafe_none (line 108) | def unsafe_none(self) -> CrossOriginOpenerPolicy:
    method same_origin_allow_popups (line 120) | def same_origin_allow_popups(self) -> CrossOriginOpenerPolicy:
    method same_origin (line 133) | def same_origin(self) -> CrossOriginOpenerPolicy:
    method noopener_allow_popups (line 146) | def noopener_allow_popups(self) -> CrossOriginOpenerPolicy:

FILE: secure/headers/cross_origin_resource_policy.py
  class CrossOriginResourcePolicy (line 23) | class CrossOriginResourcePolicy(BaseHeader):
    method header_value (line 44) | def header_value(self) -> str:
    method clear (line 48) | def clear(self) -> CrossOriginResourcePolicy:
    method value (line 58) | def value(self, value: str | CorpDirective) -> CrossOriginResourcePolicy:
    method set (line 81) | def set(self, value: str) -> CrossOriginResourcePolicy:
    method same_origin (line 89) | def same_origin(self) -> CrossOriginResourcePolicy:
    method same_site (line 94) | def same_site(self) -> CrossOriginResourcePolicy:
    method cross_origin (line 99) | def cross_origin(self) -> CrossOriginResourcePolicy:
    method _normalize_value (line 105) | def _normalize_value(value: str) -> str:

FILE: secure/headers/custom_header.py
  class CustomHeader (line 10) | class CustomHeader(BaseHeader):
    method __init__ (line 29) | def __init__(self, header: str, value: str) -> None:
    method header_value (line 41) | def header_value(self) -> str:
    method set (line 50) | def set(self, value: str) -> CustomHeader:
    method value (line 66) | def value(self, value: str) -> CustomHeader:

FILE: secure/headers/permissions_policy.py
  function _normalize_token (line 17) | def _normalize_token(raw: str, tokens_len: int) -> str | None:
  function _normalize_allowlist (line 55) | def _normalize_allowlist(tokens: tuple[str, ...]) -> str:
  class PermissionsPolicy (line 105) | class PermissionsPolicy(BaseHeader):
    method header_value (line 136) | def header_value(self) -> str:
    method value (line 151) | def value(self, value: str) -> PermissionsPolicy:
    method set (line 169) | def set(self, value: str) -> PermissionsPolicy:
    method clear (line 173) | def clear(self) -> PermissionsPolicy:
    method add_directive (line 183) | def add_directive(self, directive: str, *allowlist: str) -> Permission...
    method directive (line 214) | def directive(self, directive: str, *allowlist: str) -> PermissionsPol...
    method accelerometer (line 222) | def accelerometer(self, *allowlist: str) -> PermissionsPolicy:
    method ambient_light_sensor (line 226) | def ambient_light_sensor(self, *allowlist: str) -> PermissionsPolicy:
    method aria_notify (line 230) | def aria_notify(self, *allowlist: str) -> PermissionsPolicy:
    method attribution_reporting (line 234) | def attribution_reporting(self, *allowlist: str) -> PermissionsPolicy:
    method autoplay (line 238) | def autoplay(self, *allowlist: str) -> PermissionsPolicy:
    method bluetooth (line 242) | def bluetooth(self, *allowlist: str) -> PermissionsPolicy:
    method browsing_topics (line 246) | def browsing_topics(self, *allowlist: str) -> PermissionsPolicy:
    method compute_pressure (line 250) | def compute_pressure(self, *allowlist: str) -> PermissionsPolicy:
    method cross_origin_isolated (line 254) | def cross_origin_isolated(self, *allowlist: str) -> PermissionsPolicy:
    method fullscreen (line 258) | def fullscreen(self, *allowlist: str) -> PermissionsPolicy:
    method gamepad (line 262) | def gamepad(self, *allowlist: str) -> PermissionsPolicy:
    method geolocation (line 266) | def geolocation(self, *allowlist: str) -> PermissionsPolicy:
    method gyroscope (line 270) | def gyroscope(self, *allowlist: str) -> PermissionsPolicy:
    method hid (line 274) | def hid(self, *allowlist: str) -> PermissionsPolicy:
    method identity_credentials_get (line 278) | def identity_credentials_get(self, *allowlist: str) -> PermissionsPolicy:
    method idle_detection (line 282) | def idle_detection(self, *allowlist: str) -> PermissionsPolicy:
    method local_fonts (line 286) | def local_fonts(self, *allowlist: str) -> PermissionsPolicy:
    method magnetometer (line 290) | def magnetometer(self, *allowlist: str) -> PermissionsPolicy:
    method microphone (line 294) | def microphone(self, *allowlist: str) -> PermissionsPolicy:
    method on_device_speech_recognition (line 298) | def on_device_speech_recognition(self, *allowlist: str) -> Permissions...
    method otp_credentials (line 302) | def otp_credentials(self, *allowlist: str) -> PermissionsPolicy:
    method publickey_credentials_create (line 306) | def publickey_credentials_create(self, *allowlist: str) -> Permissions...
    method publickey_credentials_get (line 310) | def publickey_credentials_get(self, *allowlist: str) -> PermissionsPol...
    method serial (line 314) | def serial(self, *allowlist: str) -> PermissionsPolicy:
    method speaker_selection (line 318) | def speaker_selection(self, *allowlist: str) -> PermissionsPolicy:
    method storage_access (line 322) | def storage_access(self, *allowlist: str) -> PermissionsPolicy:
    method summarizer (line 326) | def summarizer(self, *allowlist: str) -> PermissionsPolicy:
    method translator (line 330) | def translator(self, *allowlist: str) -> PermissionsPolicy:
    method language_detector (line 334) | def language_detector(self, *allowlist: str) -> PermissionsPolicy:
    method usb (line 338) | def usb(self, *allowlist: str) -> PermissionsPolicy:
    method web_share (line 342) | def web_share(self, *allowlist: str) -> PermissionsPolicy:
    method window_management (line 346) | def window_management(self, *allowlist: str) -> PermissionsPolicy:
    method xr_spatial_tracking (line 350) | def xr_spatial_tracking(self, *allowlist: str) -> PermissionsPolicy:
    method battery (line 358) | def battery(self, *allowlist: str) -> PermissionsPolicy:
    method camera (line 362) | def camera(self, *allowlist: str) -> PermissionsPolicy:
    method clipboard_read (line 366) | def clipboard_read(self, *allowlist: str) -> PermissionsPolicy:
    method clipboard_write (line 370) | def clipboard_write(self, *allowlist: str) -> PermissionsPolicy:
    method display_capture (line 374) | def display_capture(self, *allowlist: str) -> PermissionsPolicy:
    method document_domain (line 378) | def document_domain(self, *allowlist: str) -> PermissionsPolicy:
    method encrypted_media (line 382) | def encrypted_media(self, *allowlist: str) -> PermissionsPolicy:
    method execution_while_not_rendered (line 386) | def execution_while_not_rendered(self, *allowlist: str) -> Permissions...
    method execution_while_out_of_viewport (line 390) | def execution_while_out_of_viewport(self, *allowlist: str) -> Permissi...
    method midi (line 394) | def midi(self, *allowlist: str) -> PermissionsPolicy:
    method navigation_override (line 398) | def navigation_override(self, *allowlist: str) -> PermissionsPolicy:
    method payment (line 402) | def payment(self, *allowlist: str) -> PermissionsPolicy:
    method picture_in_picture (line 406) | def picture_in_picture(self, *allowlist: str) -> PermissionsPolicy:
    method screen_wake_lock (line 410) | def screen_wake_lock(self, *allowlist: str) -> PermissionsPolicy:
    method sync_xhr (line 414) | def sync_xhr(self, *allowlist: str) -> PermissionsPolicy:

FILE: secure/headers/referrer_policy.py
  function _split_policies (line 17) | def _split_policies(value: str) -> list[str]:
  class ReferrerPolicy (line 39) | class ReferrerPolicy(BaseHeader):
    method header_value (line 59) | def header_value(self) -> str:
    method _add_token (line 63) | def _add_token(self, token: str) -> None:
    method add (line 67) | def add(self, value: str) -> ReferrerPolicy:
    method set (line 86) | def set(self, value: str) -> ReferrerPolicy:
    method value (line 90) | def value(self, value: str) -> ReferrerPolicy:
    method custom (line 99) | def custom(self, value: str) -> ReferrerPolicy:
    method fallback (line 103) | def fallback(self, *policies: str) -> ReferrerPolicy:
    method clear (line 113) | def clear(self) -> ReferrerPolicy:
    method no_referrer (line 120) | def no_referrer(self) -> ReferrerPolicy:
    method no_referrer_when_downgrade (line 124) | def no_referrer_when_downgrade(self) -> ReferrerPolicy:
    method origin (line 132) | def origin(self) -> ReferrerPolicy:
    method origin_when_cross_origin (line 136) | def origin_when_cross_origin(self) -> ReferrerPolicy:
    method same_origin (line 143) | def same_origin(self) -> ReferrerPolicy:
    method strict_origin (line 150) | def strict_origin(self) -> ReferrerPolicy:
    method strict_origin_when_cross_origin (line 157) | def strict_origin_when_cross_origin(self) -> ReferrerPolicy:
    method unsafe_url (line 165) | def unsafe_url(self) -> ReferrerPolicy:

FILE: secure/headers/server.py
  class Server (line 10) | class Server(BaseHeader):
    method header_value (line 30) | def header_value(self) -> str:
    method set (line 39) | def set(self, value: str) -> Server:
    method value (line 55) | def value(self, value: str) -> Server:
    method clear (line 59) | def clear(self) -> Server:

FILE: secure/headers/strict_transport_security.py
  class StrictTransportSecurity (line 20) | class StrictTransportSecurity(BaseHeader):
    method header_value (line 53) | def header_value(self) -> str:
    method _default_max_age_seconds (line 82) | def _default_max_age_seconds(self) -> int:
    method _ensure_no_newlines (line 94) | def _ensure_no_newlines(value: str) -> str:
    method clear (line 98) | def clear(self) -> StrictTransportSecurity:
    method value (line 110) | def value(self, value: str) -> StrictTransportSecurity:
    method max_age (line 122) | def max_age(self, seconds: int) -> StrictTransportSecurity:
    method include_subdomains (line 137) | def include_subdomains(self) -> StrictTransportSecurity:
    method preload (line 143) | def preload(self) -> StrictTransportSecurity:

FILE: secure/headers/x_content_type_options.py
  class XContentTypeOptions (line 18) | class XContentTypeOptions(BaseHeader):
    method header_value (line 37) | def header_value(self) -> str:
    method set (line 45) | def set(self, value: str) -> XContentTypeOptions:
    method value (line 58) | def value(self, value: str) -> XContentTypeOptions:
    method clear (line 62) | def clear(self) -> XContentTypeOptions:
    method nosniff (line 72) | def nosniff(self) -> XContentTypeOptions:

FILE: secure/headers/x_dns_prefetch_control.py
  class XDnsPrefetchControl (line 21) | class XDnsPrefetchControl(BaseHeader):
    method header_value (line 42) | def header_value(self) -> str:
    method clear (line 46) | def clear(self) -> XDnsPrefetchControl:
    method set (line 51) | def set(self, value: str) -> XDnsPrefetchControl:
    method value (line 62) | def value(self, value: str) -> XDnsPrefetchControl:
    method custom (line 66) | def custom(self, value: str) -> XDnsPrefetchControl:
    method on (line 70) | def on(self) -> XDnsPrefetchControl:
    method off (line 79) | def off(self) -> XDnsPrefetchControl:
    method allow (line 90) | def allow(self) -> XDnsPrefetchControl:
    method disable (line 94) | def disable(self) -> XDnsPrefetchControl:
    method _normalize (line 99) | def _normalize(value: str) -> str:

FILE: secure/headers/x_frame_options.py
  class XFrameOptions (line 18) | class XFrameOptions(BaseHeader):
    method header_value (line 38) | def header_value(self) -> str:
    method value (line 46) | def value(self, value: str) -> XFrameOptions:
    method set (line 65) | def set(self, value: str) -> XFrameOptions:
    method custom (line 69) | def custom(self, value: str) -> XFrameOptions:
    method clear (line 73) | def clear(self) -> XFrameOptions:
    method deny (line 82) | def deny(self) -> XFrameOptions:
    method sameorigin (line 90) | def sameorigin(self) -> XFrameOptions:
    method allow_from (line 98) | def allow_from(self, origin: str) -> XFrameOptions:

FILE: secure/headers/x_permitted_cross_domain_policies.py
  class XPermittedCrossDomainPolicies (line 37) | class XPermittedCrossDomainPolicies(BaseHeader):
    method header_value (line 69) | def header_value(self) -> str:
    method clear (line 75) | def clear(self) -> XPermittedCrossDomainPolicies:
    method value (line 80) | def value(self, value: str) -> XPermittedCrossDomainPolicies:
    method custom (line 90) | def custom(self, value: str) -> XPermittedCrossDomainPolicies:
    method set (line 94) | def set(self, value: str) -> XPermittedCrossDomainPolicies:
    method policy (line 98) | def policy(self, policy: PermittedCrossDomainPolicy) -> XPermittedCros...
    method none (line 107) | def none(self) -> XPermittedCrossDomainPolicies:
    method master_only (line 111) | def master_only(self) -> XPermittedCrossDomainPolicies:
    method by_content_type (line 115) | def by_content_type(self) -> XPermittedCrossDomainPolicies:
    method by_ftp_filename (line 119) | def by_ftp_filename(self) -> XPermittedCrossDomainPolicies:
    method all (line 123) | def all(self) -> XPermittedCrossDomainPolicies:
    method none_this_response (line 127) | def none_this_response(self) -> XPermittedCrossDomainPolicies:

FILE: secure/middleware/asgi.py
  class ASGIApp (line 19) | class ASGIApp(Protocol):
    method __call__ (line 20) | def __call__(self, scope: Scope, receive: Receive, send: Send) -> Awai...
  function _normalize_header_name (line 31) | def _normalize_header_name(name: str) -> str:
  function _normalize_header_name_bytes (line 36) | def _normalize_header_name_bytes(name: bytes) -> bytes:
  function _encode_header_name (line 41) | def _encode_header_name(name: str) -> bytes:
  function _encode_header_value (line 51) | def _encode_header_value(value: str) -> bytes:
  class SecureASGIMiddleware (line 67) | class SecureASGIMiddleware:
    method __init__ (line 100) | def __init__(
    method __call__ (line 117) | async def __call__(self, scope: Scope, receive: Receive, send: Send) -...

FILE: secure/middleware/wsgi.py
  function _normalize_header_name (line 39) | def _normalize_header_name(name: str) -> str:
  class SecureWSGIMiddleware (line 49) | class SecureWSGIMiddleware:
    method __init__ (line 83) | def __init__(
    method __call__ (line 96) | def __call__(self, environ: WSGIEnvironment, start_response: StartResp...

FILE: secure/secure.py
  class Secure (line 42) | class Secure:
    method __init__ (line 70) | def __init__(  # noqa: PLR0913
    method headers_list (line 147) | def headers_list(self) -> list[BaseHeader]:
    method headers_list (line 152) | def headers_list(self, headers: Iterable[BaseHeader]) -> None:
    method _discard_normalized_headers (line 155) | def _discard_normalized_headers(self) -> None:
    method _normalized_headers_for (line 159) | def _normalized_headers_for(
    method with_default_headers (line 173) | def with_default_headers(cls) -> Secure:
    method from_preset (line 190) | def from_preset(cls, preset: Preset) -> Secure:
    method __str__ (line 214) | def __str__(self) -> str:
    method __repr__ (line 218) | def __repr__(self) -> str:
    method validate_and_normalize_headers (line 226) | def validate_and_normalize_headers(
    method deduplicate_headers (line 282) | def deduplicate_headers(
    method allowlist_headers (line 330) | def allowlist_headers(
    method header_items (line 383) | def header_items(self) -> HeaderItems:
    method _resolved_header_items (line 398) | def _resolved_header_items(self) -> HeaderItems:
    method headers (line 409) | def headers(self) -> Mapping[str, str]:
    method set_headers (line 441) | def set_headers(self, response: ResponseProtocol) -> None:
    method set_headers_async (line 473) | async def set_headers_async(self, response: ResponseProtocol) -> None:

FILE: tests/headers/test_cache_control.py
  class TestCacheControl (line 6) | class TestCacheControl(unittest.TestCase):
    method test_default_cache_control (line 7) | def test_default_cache_control(self):
    method test_set_no_cache (line 12) | def test_set_no_cache(self):
    method test_set_max_age (line 17) | def test_set_max_age(self):
    method test_clear_cache_control (line 22) | def test_clear_cache_control(self):
    method test_multiple_directives (line 27) | def test_multiple_directives(self):

FILE: tests/headers/test_content_security_policy.py
  class TestContentSecurityPolicy (line 7) | class TestContentSecurityPolicy(unittest.TestCase):
    method test_default_csp (line 8) | def test_default_csp(self):
    method test_custom_policy (line 16) | def test_custom_policy(self):
    method test_add_script_src (line 21) | def test_add_script_src(self):
    method test_clear_policy (line 26) | def test_clear_policy(self):

FILE: tests/headers/test_cross_origin_embedder_policy.py
  class TestCrossOriginEmbedderPolicy (line 7) | class TestCrossOriginEmbedderPolicy(unittest.TestCase):
    method test_default_value (line 8) | def test_default_value(self) -> None:
    method test_builder_helpers (line 13) | def test_builder_helpers(self) -> None:
    method test_value_accepts_custom (line 19) | def test_value_accepts_custom(self) -> None:
    method test_invalid_inputs_raise (line 24) | def test_invalid_inputs_raise(self) -> None:
    method test_deterministic_output (line 29) | def test_deterministic_output(self) -> None:

FILE: tests/headers/test_cross_origin_opener_policy.py
  class TestCrossOriginOpenerPolicy (line 6) | class TestCrossOriginOpenerPolicy(unittest.TestCase):
    method test_default_coop (line 7) | def test_default_coop(self):
    method test_set_custom_policy (line 12) | def test_set_custom_policy(self):
    method test_same_origin (line 17) | def test_same_origin(self):
    method test_same_origin_allow_popups (line 22) | def test_same_origin_allow_popups(self):
    method test_unsafe_none (line 27) | def test_unsafe_none(self):
    method test_clear_policy (line 32) | def test_clear_policy(self):

FILE: tests/headers/test_custom_header.py
  class TestCustomHeader (line 6) | class TestCustomHeader(unittest.TestCase):
    method test_custom_header_initialization (line 7) | def test_custom_header_initialization(self):
    method test_set_custom_header_value (line 13) | def test_set_custom_header_value(self):
    method test_method_chaining (line 19) | def test_method_chaining(self):

FILE: tests/headers/test_header_contracts_extended.py
  class HeaderSpec (line 26) | class HeaderSpec:
  class TestHeaderContracts (line 38) | class TestHeaderContracts(unittest.TestCase):
    method test_default_values (line 225) | def test_default_values(self) -> None:
    method test_header_names (line 231) | def test_header_names(self) -> None:
    method test_clear_resets_default (line 237) | def test_clear_resets_default(self) -> None:
    method test_builder_helpers (line 246) | def test_builder_helpers(self) -> None:
    method test_invalid_inputs_raise (line 252) | def test_invalid_inputs_raise(self) -> None:
    method test_deterministic_output (line 257) | def test_deterministic_output(self) -> None:

FILE: tests/headers/test_permissions_policy.py
  class TestPermissionsPolicy (line 6) | class TestPermissionsPolicy(unittest.TestCase):
    method test_default_permissions_policy (line 7) | def test_default_permissions_policy(self):
    method test_custom_permissions_policy (line 15) | def test_custom_permissions_policy(self):
    method test_clear_permissions_policy (line 20) | def test_clear_permissions_policy(self):
    method test_add_directive (line 28) | def test_add_directive(self):

FILE: tests/headers/test_referrer_policy.py
  class TestReferrerPolicy (line 6) | class TestReferrerPolicy(unittest.TestCase):
    method test_default_referrer_policy (line 7) | def test_default_referrer_policy(self):
    method test_set_custom_policy (line 12) | def test_set_custom_policy(self):
    method test_no_referrer (line 17) | def test_no_referrer(self):
    method test_no_referrer_when_downgrade (line 22) | def test_no_referrer_when_downgrade(self):
    method test_origin (line 27) | def test_origin(self):
    method test_origin_when_cross_origin (line 32) | def test_origin_when_cross_origin(self):
    method test_same_origin (line 37) | def test_same_origin(self):
    method test_strict_origin (line 42) | def test_strict_origin(self):
    method test_strict_origin_when_cross_origin (line 47) | def test_strict_origin_when_cross_origin(self):
    method test_unsafe_url (line 52) | def test_unsafe_url(self):
    method test_clear_policy (line 57) | def test_clear_policy(self):

FILE: tests/headers/test_server.py
  class TestServerHeader (line 6) | class TestServerHeader(unittest.TestCase):
    method test_default_server (line 7) | def test_default_server(self):
    method test_set_custom_server (line 12) | def test_set_custom_server(self):
    method test_clear_server (line 17) | def test_clear_server(self):

FILE: tests/headers/test_strict_transport_security.py
  class TestStrictTransportSecurity (line 6) | class TestStrictTransportSecurity(unittest.TestCase):
    method test_default_hsts (line 7) | def test_default_hsts(self):
    method test_custom_max_age (line 13) | def test_custom_max_age(self):
    method test_preload (line 18) | def test_preload(self):
    method test_include_subdomains (line 23) | def test_include_subdomains(self):

FILE: tests/headers/test_x_content_type_options.py
  class TestXContentTypeOptions (line 6) | class TestXContentTypeOptions(unittest.TestCase):
    method test_default_x_content_type_options (line 7) | def test_default_x_content_type_options(self):
    method test_set_custom_value (line 12) | def test_set_custom_value(self):
    method test_nosniff (line 17) | def test_nosniff(self):
    method test_clear (line 22) | def test_clear(self):

FILE: tests/headers/test_x_frame_options.py
  class TestXFrameOptions (line 6) | class TestXFrameOptions(unittest.TestCase):
    method test_default_x_frame_options (line 7) | def test_default_x_frame_options(self):
    method test_set_deny (line 12) | def test_set_deny(self):
    method test_set_sameorigin (line 17) | def test_set_sameorigin(self):

FILE: tests/middleware/test_middleware.py
  function _find_header_values (line 8) | def _find_header_values(headers: list[tuple[str, str]], name: str) -> li...
  class _CapturedWSGI (line 13) | class _CapturedWSGI(TypedDict):
  function _run_wsgi_with_middleware (line 20) | def _run_wsgi_with_middleware(middleware) -> _CapturedWSGI:
  function test_wsgi_overwrites_existing_header (line 43) | def test_wsgi_overwrites_existing_header():
  function test_wsgi_multi_ok_appends_existing_header (line 58) | def test_wsgi_multi_ok_appends_existing_header():
  function test_wsgi_preserves_status_and_extra_headers (line 73) | def test_wsgi_preserves_status_and_extra_headers():
  function _headers_by_name_bytes (line 88) | def _headers_by_name_bytes(headers: list[tuple[bytes, bytes]], name: byt...
  function _http_app (line 93) | def _http_app(response_headers: list[tuple[bytes, bytes]]):
  class _HTTPResponseStart (line 107) | class _HTTPResponseStart(TypedDict):
  function _run_asgi (line 113) | async def _run_asgi(scope: dict[str, Any], app) -> list[dict[str, Any]]:
  function test_asgi_overwrites_numeric_headers (line 126) | def test_asgi_overwrites_numeric_headers():
  function test_asgi_multi_ok_appends_default_csp (line 149) | def test_asgi_multi_ok_appends_default_csp():
  function test_asgi_ignores_non_http_scopes (line 166) | def test_asgi_ignores_non_http_scopes():
  function test_asgi_multi_ok_custom_header (line 176) | def test_asgi_multi_ok_custom_header():

FILE: tests/middleware/test_secure_protocols.py
  class HeadersOnlyResponse (line 11) | class HeadersOnlyResponse:
    method __init__ (line 12) | def __init__(self) -> None:
  class SetHeaderResponse (line 16) | class SetHeaderResponse:
    method __init__ (line 17) | def __init__(self) -> None:
    method set_header (line 20) | def set_header(self, key: str, value: str) -> None:
  class AsyncSetHeaderResponse (line 24) | class AsyncSetHeaderResponse:
    method __init__ (line 25) | def __init__(self) -> None:
    method set_header (line 28) | async def set_header(self, key: str, value: str) -> None:
  class AsyncOnlySetHeader (line 32) | class AsyncOnlySetHeader:
    method set_header (line 33) | async def set_header(self, key: str, value: str) -> None:
  class TestSetHeaders (line 37) | class TestSetHeaders(unittest.TestCase):
    method test_headers_mapping_path_applies_headers (line 38) | def test_headers_mapping_path_applies_headers(self) -> None:
    method test_set_headers_prefers_set_header_method (line 46) | def test_set_headers_prefers_set_header_method(self) -> None:
    method test_set_headers_async_accepts_async_set_header (line 54) | def test_set_headers_async_accepts_async_set_header(self) -> None:
    method test_set_headers_rejects_async_method_in_sync_context (line 62) | def test_set_headers_rejects_async_method_in_sync_context(self) -> None:

FILE: tests/secure_tests/test_exports.py
  class TestExportSurface (line 8) | class TestExportSurface(unittest.TestCase):
    method test_secure_package_exports (line 9) | def test_secure_package_exports(self) -> None:
    method test_headers_package_exports (line 37) | def test_headers_package_exports(self) -> None:
    method test_middleware_package_exports (line 61) | def test_middleware_package_exports(self) -> None:

FILE: tests/secure_tests/test_headers.py
  class TestHeaderConsistency (line 22) | class TestHeaderConsistency(unittest.TestCase):
    method test_value_methods_reject_crlf (line 23) | def test_value_methods_reject_crlf(self) -> None:
    method test_clear_restores_default_values (line 47) | def test_clear_restores_default_values(self) -> None:
    method test_cache_control_canonical_order (line 75) | def test_cache_control_canonical_order(self) -> None:
    method test_content_security_policy_values_deduplicate (line 80) | def test_content_security_policy_values_deduplicate(self) -> None:
    method test_permissions_policy_wildcard_must_be_alone (line 85) | def test_permissions_policy_wildcard_must_be_alone(self) -> None:
    method test_referrer_policy_add_is_idempotent (line 90) | def test_referrer_policy_add_is_idempotent(self) -> None:
    method test_xdns_prefetch_control_normalizes_values (line 94) | def test_xdns_prefetch_control_normalizes_values(self) -> None:
    method test_x_permitted_policy_rejects_unknown (line 102) | def test_x_permitted_policy_rejects_unknown(self) -> None:

FILE: tests/secure_tests/test_internal_helpers.py
  class _AsyncHeadersMapping (line 12) | class _AsyncHeadersMapping:
    method __init__ (line 13) | def __init__(self) -> None:
    method __setitem__ (line 16) | async def __setitem__(self, key: str, value: str) -> None:
  class _AsyncHeadersResponse (line 20) | class _AsyncHeadersResponse:
    method __init__ (line 21) | def __init__(self) -> None:
  class TestInternalHelpers (line 25) | class TestInternalHelpers(unittest.TestCase):
    method test_normalize_header_items_warns_and_drops_invalid_names (line 26) | def test_normalize_header_items_warns_and_drops_invalid_names(self) ->...
    method test_normalize_header_items_preserves_obs_text_when_allowed (line 38) | def test_normalize_header_items_preserves_obs_text_when_allowed(self) ...
    method test_allowlist_header_objects_warn_keeps_unexpected_headers (line 46) | def test_allowlist_header_objects_warn_keeps_unexpected_headers(self) ...
    method test_deduplicate_header_objects_preserves_multi_ok_order (line 60) | def test_deduplicate_header_objects_preserves_multi_ok_order(self) -> ...
    method test_set_headers_async_helper_awaits_async_headers_mapping (line 81) | def test_set_headers_async_helper_awaits_async_headers_mapping(self) -...

FILE: tests/secure_tests/test_secure.py
  class MockResponse (line 22) | class MockResponse:
    method __init__ (line 23) | def __init__(self) -> None:
    method set_header (line 26) | def set_header(self, key: str, value: str) -> None:
  class MockResponseWithSetHeader (line 31) | class MockResponseWithSetHeader:
    method __init__ (line 32) | def __init__(self) -> None:
    method set_header (line 36) | def set_header(self, key: str, value: str) -> None:
  class MockResponseAsyncSetHeader (line 41) | class MockResponseAsyncSetHeader:
    method __init__ (line 42) | def __init__(self) -> None:
    method set_header (line 46) | async def set_header(self, key: str, value: str) -> None:
  class MockResponseNoHeaders (line 51) | class MockResponseNoHeaders:
  class _AsyncHeadersMapping (line 55) | class _AsyncHeadersMapping:
    method __init__ (line 56) | def __init__(self) -> None:
    method __setitem__ (line 59) | async def __setitem__(self, key: str, value: str) -> None:
  class MockAsyncHeadersResponse (line 63) | class MockAsyncHeadersResponse:
    method __init__ (line 64) | def __init__(self) -> None:
    method set_header (line 67) | async def set_header(self, key: str, value: str) -> None:
  class MockResponseRaiseSetHeader (line 72) | class MockResponseRaiseSetHeader:
    method set_header (line 73) | def set_header(self, key: str, value: str) -> None:
  class MockResponseAwaitableSetHeader (line 77) | class MockResponseAwaitableSetHeader:
    method __init__ (line 78) | def __init__(self) -> None:
    method set_header (line 81) | def set_header(self, key: str, value: str) -> Awaitable[None]:
  function _expected_basic_csp_value (line 95) | def _expected_basic_csp_value() -> str:
  class TestSecure (line 113) | class TestSecure(unittest.TestCase):
    method setUp (line 114) | def setUp(self) -> None:
    method test_with_default_headers (line 123) | def test_with_default_headers(self) -> None:
    method test_with_default_headers_matches_balanced_preset (line 174) | def test_with_default_headers_matches_balanced_preset(self) -> None:
    method test_balanced_preset_omits_cache_control (line 180) | def test_balanced_preset_omits_cache_control(self) -> None:
    method test_from_preset_basic (line 186) | def test_from_preset_basic(self) -> None:
    method test_from_preset_strict (line 240) | def test_from_preset_strict(self) -> None:
    method test_custom_headers (line 291) | def test_custom_headers(self) -> None:
    method test_async_set_headers (line 311) | def test_async_set_headers(self) -> None:
    method test_set_headers_with_set_header_method (line 340) | def test_set_headers_with_set_header_method(self) -> None:
    method test_set_headers_with_headers_dict (line 350) | def test_set_headers_with_headers_dict(self) -> None:
    method test_set_headers_async_with_async_set_header (line 358) | def test_set_headers_async_with_async_set_header(self) -> None:
    method test_set_headers_async_with_headers_dict (line 372) | def test_set_headers_async_with_headers_dict(self) -> None:
    method test_validate_and_normalize_headers_drops_invalid_entries (line 380) | def test_validate_and_normalize_headers_drops_invalid_entries(self) ->...
    method test_validate_and_normalize_headers_applies_normalized_values (line 396) | def test_validate_and_normalize_headers_applies_normalized_values(self...
    method test_set_headers_missing_interface (line 415) | def test_set_headers_missing_interface(self) -> None:
    method test_set_headers_with_async_set_header_in_sync_context (line 428) | def test_set_headers_with_async_set_header_in_sync_context(self) -> None:
    method test_set_headers_overwrites_existing_headers (line 434) | def test_set_headers_overwrites_existing_headers(self) -> None:
    method test_custom_header_inclusion (line 446) | def test_custom_header_inclusion(self) -> None:
    method test_headers_property (line 458) | def test_headers_property(self) -> None:
    method test_str_representation (line 466) | def test_str_representation(self) -> None:
    method test_repr_representation (line 475) | def test_repr_representation(self) -> None:
    method test_str_representation_uses_normalized_values (line 483) | def test_str_representation_uses_normalized_values(self) -> None:
    method test_package_exports_header_constants (line 493) | def test_package_exports_header_constants(self) -> None:
    method test_invalid_preset (line 499) | def test_invalid_preset(self) -> None:
    method test_empty_secure_instance (line 506) | def test_empty_secure_instance(self) -> None:
    method test_multiple_custom_headers (line 514) | def test_multiple_custom_headers(self) -> None:
    method test_custom_strict_transport_security (line 531) | def test_custom_strict_transport_security(self) -> None:
    method test_setting_headers_on_response_with_both_headers_and_set_header (line 545) | def test_setting_headers_on_response_with_both_headers_and_set_header(...
    method test_header_order (line 572) | def test_header_order(self) -> None:
    method test_set_headers_async_with_sync_set_header (line 584) | def test_set_headers_async_with_sync_set_header(self) -> None:
    method test_set_headers_with_no_headers_or_set_header (line 597) | def test_set_headers_with_no_headers_or_set_header(self) -> None:
    method test_headers_list_property (line 610) | def test_headers_list_property(self) -> None:
    method test_headers_property_with_no_headers (line 623) | def test_headers_property_with_no_headers(self) -> None:
    method test_headers_property_tracks_builder_mutation_after_access (line 628) | def test_headers_property_tracks_builder_mutation_after_access(self) -...
    method test_allowlist_headers_drop_unexpected (line 639) | def test_allowlist_headers_drop_unexpected(self) -> None:
    method test_allowlist_headers_raises_on_unexpected (line 647) | def test_allowlist_headers_raises_on_unexpected(self) -> None:
    method test_allowlist_accepts_default_balanced_headers (line 654) | def test_allowlist_accepts_default_balanced_headers(self) -> None:
    method test_allowlist_respects_allow_x_prefixed (line 661) | def test_allowlist_respects_allow_x_prefixed(self) -> None:
    method test_deduplicate_concat_merges_cache_control (line 668) | def test_deduplicate_concat_merges_cache_control(self) -> None:
    method test_deduplicate_headers_raise_on_duplicate (line 683) | def test_deduplicate_headers_raise_on_duplicate(self) -> None:
    method test_validate_and_normalize_headers_strict_rejects_crlf (line 695) | def test_validate_and_normalize_headers_strict_rejects_crlf(self) -> N...
    method test_validate_and_normalize_headers_is_cleared_by_headers_list_mutation (line 706) | def test_validate_and_normalize_headers_is_cleared_by_headers_list_mut...
    method test_validate_and_normalize_headers_is_cleared_by_builder_mutation (line 721) | def test_validate_and_normalize_headers_is_cleared_by_builder_mutation...
    method test_headers_property_raises_on_duplicates (line 731) | def test_headers_property_raises_on_duplicates(self) -> None:
    method test_set_headers_wraps_setter_errors (line 743) | def test_set_headers_wraps_setter_errors(self) -> None:
    method test_set_headers_async_wraps_setter_errors (line 751) | def test_set_headers_async_wraps_setter_errors(self) -> None:
    method test_set_headers_runtime_error_on_async_setter (line 762) | def test_set_headers_runtime_error_on_async_setter(self) -> None:
    method test_set_headers_async_handles_async_headers_mapping (line 770) | def test_set_headers_async_handles_async_headers_mapping(self) -> None:
Condensed preview — 80 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (368K chars).
[
  {
    "path": ".github/workflows/tests.yml",
    "chars": 704,
    "preview": "name: Unit Tests\n\non:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  tests:\n    runs-on: ubuntu-latest\n    stra"
  },
  {
    "path": ".gitignore",
    "chars": 3155,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 5051,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 1256,
    "preview": "# Code of Conduct\n\nThis Code of Conduct applies to all `secure` community spaces.\n\n## Our Pledge\n\nWe’re committed to a w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3348,
    "preview": "# Contributing\n\nThanks for helping make `secure` better. The following guidance keeps contributions aligned with the pro"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) 2018-2024 Caleb Kinney\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "README.md",
    "chars": 7374,
    "preview": "# secure\n\n[![PyPI Version](https://img.shields.io/pypi/v/secure.svg)](https://pypi.org/project/secure/) [![Python Versio"
  },
  {
    "path": "docs/README.md",
    "chars": 1419,
    "preview": "# Documentation\n\nUse this index when you know what you need. For a first pass, start with the [top-level README](../READ"
  },
  {
    "path": "docs/configuration.md",
    "chars": 4794,
    "preview": "# Configuration Guide\n\nThis guide covers the parts of `secure` you are most likely to customize after the quick start. K"
  },
  {
    "path": "docs/frameworks.md",
    "chars": 15916,
    "preview": "# Framework Integration\n\n`secure` keeps the same `Secure` object across frameworks. What changes is how you attach it.\n\n"
  },
  {
    "path": "docs/headers/cache_control.md",
    "chars": 4383,
    "preview": "# Cache-Control\n\n## What it does\n\n`Cache-Control` is a comma-separated list of **directives** that control caching behav"
  },
  {
    "path": "docs/headers/content_security_policy.md",
    "chars": 6891,
    "preview": "# Content-Security-Policy (CSP)\n\n## What it does\n\nThe `Content-Security-Policy` (CSP) response header helps mitigate cro"
  },
  {
    "path": "docs/headers/cross-origin-resource-policy.md",
    "chars": 3012,
    "preview": "# Cross-Origin-Resource-Policy (CORP)\n\n## What it does\n\nThe `Cross-Origin-Resource-Policy` (CORP) response header lets a"
  },
  {
    "path": "docs/headers/cross_origin_embedder_policy.md",
    "chars": 4334,
    "preview": "# Cross-Origin-Embedder-Policy (COEP)\n\n## What it does\n\nThe **`Cross-Origin-Embedder-Policy`** response header configure"
  },
  {
    "path": "docs/headers/cross_origin_opener_policy.md",
    "chars": 3197,
    "preview": "# Cross-Origin-Opener-Policy\n\n## What it does\n\nThe `Cross-Origin-Opener-Policy` (COOP) response header controls whether "
  },
  {
    "path": "docs/headers/custom_header.md",
    "chars": 2179,
    "preview": "# CustomHeader Class\n\n## What it does\n\nThe `CustomHeader` class lets you create arbitrary HTTP response headers when `se"
  },
  {
    "path": "docs/headers/dns_prefetch_control.md",
    "chars": 4041,
    "preview": "# X-DNS-Prefetch-Control\n\n## What it does\n\n`X-DNS-Prefetch-Control` controls **DNS prefetching**, where browsers may pro"
  },
  {
    "path": "docs/headers/permissions_policy.md",
    "chars": 3466,
    "preview": "# Permissions-Policy\n\n## What it does\n\nThe `Permissions-Policy` HTTP response header lets you enable or disable access t"
  },
  {
    "path": "docs/headers/referrer_policy.md",
    "chars": 6115,
    "preview": "# Referrer-Policy\n\n## What it does\n\nThe `Referrer-Policy` response header controls how much referrer information (sent v"
  },
  {
    "path": "docs/headers/server.md",
    "chars": 2713,
    "preview": "# Server Header\n\n## What it does\n\nThe `Server` header can reveal details about the software handling the request. In `se"
  },
  {
    "path": "docs/headers/strict_transport_security.md",
    "chars": 4374,
    "preview": "# Strict-Transport-Security (HSTS)\n\n## What it does\n\nThe `Strict-Transport-Security` (HSTS) header tells browsers that a"
  },
  {
    "path": "docs/headers/x-permitted-cross-domain-policies.md",
    "chars": 4768,
    "preview": "# X-Permitted-Cross-Domain-Policies\n\n## What it does\n\n`X-Permitted-Cross-Domain-Policies` is a **response header** that "
  },
  {
    "path": "docs/headers/x_content_type_options.md",
    "chars": 3090,
    "preview": "# X-Content-Type-Options\n\n## What it does\n\nThe `X-Content-Type-Options` header tells browsers to **respect the MIME type"
  },
  {
    "path": "docs/headers/x_frame_options.md",
    "chars": 3539,
    "preview": "# X-Frame-Options\n\n## What it does\n\n`X-Frame-Options` is an HTTP **response header** that tells supporting browsers whet"
  },
  {
    "path": "docs/installation.md",
    "chars": 568,
    "preview": "# Installation\n\n`secure` requires Python 3.10 or newer and has no external dependencies.\n\nInstall it with `uv` or `pip`:"
  },
  {
    "path": "docs/migration.md",
    "chars": 2383,
    "preview": "# v2 Migration Notes\n\nThe first stable v2 release is `2.0.1`. Skip `2.0.0`.\n\nIf your application already uses `Secure` t"
  },
  {
    "path": "docs/security_considerations.md",
    "chars": 7932,
    "preview": "# Security Considerations\n\n## Overview\n\nSecurity headers are one part of a web application's security posture. They help"
  },
  {
    "path": "docs/usage.md",
    "chars": 4046,
    "preview": "# Usage\n\n`Secure` is the public entry point. Configure it once, then reuse it wherever your framework gives you access t"
  },
  {
    "path": "pyproject.toml",
    "chars": 2748,
    "preview": "[build-system]\nrequires = [\"setuptools>=77\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"secure\""
  },
  {
    "path": "secure/__init__.py",
    "chars": 1465,
    "preview": "from secure.secure import (\n    COMMA_JOIN_OK,\n    DEFAULT_ALLOWED_HEADERS,\n    MULTI_OK,\n    Preset,\n    Secure,\n)\n\nfro"
  },
  {
    "path": "secure/_internal/__init__.py",
    "chars": 61,
    "preview": "\"\"\"Private implementation details for the secure package.\"\"\"\n"
  },
  {
    "path": "secure/_internal/configured_headers.py",
    "chars": 4278,
    "preview": "from __future__ import annotations\n\nfrom types import MappingProxyType\nfrom typing import TYPE_CHECKING, Any, TypeAlias,"
  },
  {
    "path": "secure/_internal/constants.py",
    "chars": 1111,
    "preview": "from __future__ import annotations\n\nimport re\n\n# Headers that may appear multiple times as separate fields.\nMULTI_OK: fr"
  },
  {
    "path": "secure/_internal/emit.py",
    "chars": 4677,
    "preview": "from __future__ import annotations\n\nimport inspect\nfrom typing import TYPE_CHECKING, cast\n\nif TYPE_CHECKING:\n    from co"
  },
  {
    "path": "secure/_internal/normalize.py",
    "chars": 4702,
    "preview": "from __future__ import annotations\n\nfrom dataclasses import dataclass\nimport logging\nfrom types import MappingProxyType\n"
  },
  {
    "path": "secure/_internal/policy.py",
    "chars": 5617,
    "preview": "from __future__ import annotations\n\nfrom collections import defaultdict\nimport logging\nfrom typing import TYPE_CHECKING\n"
  },
  {
    "path": "secure/_internal/presets.py",
    "chars": 4178,
    "preview": "from __future__ import annotations\n\nfrom enum import Enum\n\nfrom ..headers import (\n    CacheControl,\n    ContentSecurity"
  },
  {
    "path": "secure/_internal/types.py",
    "chars": 602,
    "preview": "from __future__ import annotations\n\nfrom typing import Literal, Protocol, TypeAlias\n\nOnInvalidPolicy = Literal[\"drop\", \""
  },
  {
    "path": "secure/headers/__init__.py",
    "chars": 1194,
    "preview": "from .base_header import BaseHeader\nfrom .cache_control import CacheControl\nfrom .content_security_policy import Content"
  },
  {
    "path": "secure/headers/_validation.py",
    "chars": 569,
    "preview": "from __future__ import annotations\n\n\ndef normalize_header_value(value: str, *, what: str = \"header value\") -> str:\n    \""
  },
  {
    "path": "secure/headers/base_header.py",
    "chars": 4835,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/cache_control.py",
    "chars": 11610,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/content_security_policy.py",
    "chars": 14866,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/cross_origin_embedder_policy.py",
    "chars": 4377,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/cross_origin_opener_policy.py",
    "chars": 5820,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/cross_origin_resource_policy.py",
    "chars": 4233,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/custom_header.py",
    "chars": 2082,
    "preview": "from __future__ import annotations  # type: ignore\n\nfrom dataclasses import dataclass, field\n\nfrom secure.headers._valid"
  },
  {
    "path": "secure/headers/permissions_policy.py",
    "chars": 18114,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/referrer_policy.py",
    "chars": 6478,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/server.py",
    "chars": 2313,
    "preview": "from __future__ import annotations  # type: ignore\n\nfrom dataclasses import dataclass, field\n\nfrom secure.headers._valid"
  },
  {
    "path": "secure/headers/strict_transport_security.py",
    "chars": 6243,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/x_content_type_options.py",
    "chars": 3075,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/x_dns_prefetch_control.py",
    "chars": 3782,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/x_frame_options.py",
    "chars": 4254,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/headers/x_permitted_cross_domain_policies.py",
    "chars": 4818,
    "preview": "# Security header recommendations and information from the MDN Web Docs and the OWASP Secure Headers Project\n# https://d"
  },
  {
    "path": "secure/middleware/__init__.py",
    "chars": 185,
    "preview": "from __future__ import annotations\n\nfrom .asgi import SecureASGIMiddleware\nfrom .wsgi import SecureWSGIMiddleware\n\n__all"
  },
  {
    "path": "secure/middleware/asgi.py",
    "chars": 5768,
    "preview": "from __future__ import annotations\n\nfrom collections.abc import Awaitable, Callable, Iterable, MutableMapping\nfrom typin"
  },
  {
    "path": "secure/middleware/wsgi.py",
    "chars": 4981,
    "preview": "from __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom types import TracebackType\nfrom typing imp"
  },
  {
    "path": "secure/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "secure/secure.py",
    "chars": 18123,
    "preview": "from __future__ import annotations\n\nimport logging\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from collecti"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/headers/test_cache_control.py",
    "chars": 1317,
    "preview": "import unittest\n\nfrom secure.headers import CacheControl\n\n\nclass TestCacheControl(unittest.TestCase):\n    def test_defau"
  },
  {
    "path": "tests/headers/test_content_security_policy.py",
    "chars": 1280,
    "preview": "import unittest\n\nfrom secure.headers import ContentSecurityPolicy\nfrom secure.headers.base_header import HeaderDefaultVa"
  },
  {
    "path": "tests/headers/test_cross_origin_embedder_policy.py",
    "chars": 1681,
    "preview": "import unittest\n\nfrom secure.headers import CrossOriginEmbedderPolicy\nfrom secure.headers.base_header import HeaderDefau"
  },
  {
    "path": "tests/headers/test_cross_origin_opener_policy.py",
    "chars": 1460,
    "preview": "import unittest\n\nfrom secure.headers import CrossOriginOpenerPolicy\n\n\nclass TestCrossOriginOpenerPolicy(unittest.TestCas"
  },
  {
    "path": "tests/headers/test_custom_header.py",
    "chars": 1056,
    "preview": "import unittest\n\nfrom secure.headers import CustomHeader\n\n\nclass TestCustomHeader(unittest.TestCase):\n    def test_custo"
  },
  {
    "path": "tests/headers/test_header_contracts_extended.py",
    "chars": 11674,
    "preview": "from collections.abc import Callable\nfrom dataclasses import dataclass\nfrom typing import ClassVar\nimport unittest\n\nfrom"
  },
  {
    "path": "tests/headers/test_permissions_policy.py",
    "chars": 1241,
    "preview": "import unittest\n\nfrom secure.headers import PermissionsPolicy\n\n\nclass TestPermissionsPolicy(unittest.TestCase):\n    def "
  },
  {
    "path": "tests/headers/test_referrer_policy.py",
    "chars": 2864,
    "preview": "import unittest\n\nfrom secure.headers import ReferrerPolicy\n\n\nclass TestReferrerPolicy(unittest.TestCase):\n    def test_d"
  },
  {
    "path": "tests/headers/test_server.py",
    "chars": 759,
    "preview": "import unittest\n\nfrom secure.headers import Server\n\n\nclass TestServerHeader(unittest.TestCase):\n    def test_default_ser"
  },
  {
    "path": "tests/headers/test_strict_transport_security.py",
    "chars": 1087,
    "preview": "import unittest\n\nfrom secure.headers import StrictTransportSecurity\n\n\nclass TestStrictTransportSecurity(unittest.TestCas"
  },
  {
    "path": "tests/headers/test_x_content_type_options.py",
    "chars": 1162,
    "preview": "import unittest\n\nfrom secure.headers import XContentTypeOptions\n\n\nclass TestXContentTypeOptions(unittest.TestCase):\n    "
  },
  {
    "path": "tests/headers/test_x_frame_options.py",
    "chars": 722,
    "preview": "import unittest\n\nfrom secure.headers import XFrameOptions\n\n\nclass TestXFrameOptions(unittest.TestCase):\n    def test_def"
  },
  {
    "path": "tests/middleware/test_middleware.py",
    "chars": 6038,
    "preview": "import asyncio\nfrom typing import Any, Literal, TypedDict, cast\n\nfrom secure import CustomHeader, Secure\nfrom secure.mid"
  },
  {
    "path": "tests/middleware/test_secure_protocols.py",
    "chars": 2067,
    "preview": "import asyncio\nfrom typing import TYPE_CHECKING\nimport unittest\n\nfrom secure import CustomHeader, Secure\n\nif TYPE_CHECKI"
  },
  {
    "path": "tests/secure_tests/__init__.py",
    "chars": 55,
    "preview": "\"\"\"Test suite for the top-level ``secure`` package.\"\"\"\n"
  },
  {
    "path": "tests/secure_tests/test_exports.py",
    "chars": 2140,
    "preview": "import unittest\n\nimport secure\nimport secure.headers\nimport secure.middleware\n\n\nclass TestExportSurface(unittest.TestCas"
  },
  {
    "path": "tests/secure_tests/test_headers.py",
    "chars": 5086,
    "preview": "import unittest\n\nfrom secure.headers import (\n    CacheControl,\n    ContentSecurityPolicy,\n    CrossOriginEmbedderPolicy"
  },
  {
    "path": "tests/secure_tests/test_internal_helpers.py",
    "chars": 2930,
    "preview": "import asyncio\nimport unittest\nfrom unittest import mock\n\nfrom secure import DEFAULT_ALLOWED_HEADERS, MULTI_OK\nfrom secu"
  },
  {
    "path": "tests/secure_tests/test_secure.py",
    "chars": 31352,
    "preview": "import asyncio\nfrom collections.abc import Awaitable, Callable, Generator\nimport unittest\n\nimport secure as secure_pkg\nf"
  }
]

About this extraction

This page contains the full source code of the TypeError/secure.py GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 80 files (340.1 KB), approximately 78.5k tokens, and a symbol index with 510 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!