Skip to main content

Sensible defaults to catch bad behavior

Project description

pyramid-sanity

pyramid-sanity is a Pyramid extension that catches certain crashes caused by badly formed requests, turning them into 400: Bad Request responses instead.

It also prevents apps from returning HTTP redirects with badly encoded locations that can crash WSGI servers.

The aim is to have sensible defaults to make it easier to write a reliable Pyramid app.

For details of all the errors and fixes, and how to reproduce them see: Error details.

Usage

with Configurator() as config:
    config.add_settings({
        # See the section below for all settings...        
        "pyramid_sanity.check_form": False,
    })

    # Add this as near to the end of your config as possible:
    config.include("pyramid_sanity")

Settings

By default all fixes are enabled. You can disable them individually with settings:

config.add_settings({
    # Don't check for badly declared forms.
    "pyramid_sanity.check_form": False,

    # Don't check for badly encoded query params.
    "pyramid_sanity.check_params": False,

    # Don't check for badly encoded URL paths.
    "pyramid_sanity.check_path": False,

    # Don't safely encode redirect locations.
    "pyramid_sanity.ascii_safe_redirects": False
})

You can set pyramid_sanity.disable_all to True to disable all of the fixes, then enable only certain fixes one by one:

config.add_settings({
    # Disable all fixes.
    "pyramid_sanity.disable_all": True,

    # Enable only the badly encoded query params fix.
    "pyramid_sanity.check_params": True,
})

Exceptions

All exceptions returned by pyramid-sanity are subclasses of pyramid_sanity.exceptions.SanityException, which is a subclass of pyramid.httpexceptions.HTTPBadRequest.

This means all pyramid-sanity exceptions trigger 400: Bad Request responses.

Different exception subclasses are returned for different problems, so you can register custom exception views to handle them if you want:

Exception Returned for
pyramid_sanity.exceptions.InvalidQueryString Badly encoded query params
pyramid_sanity.exceptions.InvalidFormData Bad form posts
pyramid_sanity.exceptions.InvalidURL Badly encoded URL paths

Tween ordering

pyramid-sanity uses a number of Pyramid tweens to do its work. It's important that your app's tween chain has:

  • Our tweens that check for errors in the request, first
  • Our tweens that check for errors in the output of your app, last

The easiest way to achieve this is to include config.include("pyramid_sanity") as late as possible in your config. This uses Pyramid's "best effort" implicit tween ordering to add the tweens and should work as long as your app doesn't add any more tweens, or include any extensions that add tweens, afterwards.

You can to check the order of tweens in your app with Pyramid's ptweens command. As long as there are no tweens which access request.GET or request.POST above the input checking tweens, or generate redirects below output checking tweens, you should be fine.

You can force the order with Pyramid's explicit tween ordering if you need to.

Tweens that raise non-ASCII redirects

pyramid-sanity protects against non-ASCII redirects raised by your app's views by safely encoding them, but it can't protect against other tweens that raise non-ASCII redirects.

For example this tween might cause a WSGI server (like Gunicorn) that's serving your app to crash with UnicodeEncodeError:

def non_ascii_redirecting_tween_factory(handler, registry):
    def non_ascii_redirecting_tween(request):
        from pyramid.httpexceptions import HTTPFound
        raise HTTPFound(location="http://example.com/€/☃")
    return non_ascii_redirecting_tween

You'll just have to make sure that your app doesn't have any tweens that do this! Tweens should encode any redirect locations that they generate, like this.

Error details

If you would like to reproduce the errors an example app is given at the end of this section. All of the presented curl commands work with this app.

Badly encoded query parameters makes request.GET crash

curl 'http://localhost:6543/foo?q=%FC'

By default

WebOb raises UnicodeDecodeError. As there is no built-in exception view for this exception the app crashes.

With pyramid-sanity

A pyramid_sanity.exceptions.InvalidQueryString is returned which results in a 400: Bad Request response.

Related issues:

A badly encoded path can cause a crash

curl 'http://localhost:6543/%FC'

By default

Pyramid raises pyramid.exceptions.URLDecodeError. As there is no built-in exception view for this exception the app crashes.

With pyramid-sanity

A pyramid_sanity.exceptions.InvalidURL is returned which results in a 400: Bad Request response.

Related issues

Bad or missing multipart boundary declarations make request.POST crash

curl --request POST --url http://localhost:6543/foo --header 'content-type: multipart/form-data'

By default

WebOb raises an uncaught ValueError. As there is no built-in exception view for this exception the app crashes.

With pyramid-sanity

A pyramid_sanity.exceptions.InvalidFormData is returned which results in a 400: Bad Request response.

Related issues:

Issuing redirects containing a non-ASCII location crashes the WSGI server

curl http://localhost:6543/redirect

By default

The app will emit the redirect successfully, but the WSGI server running the app may crash. With the example app below wsgiref.simple_server raises an uncaught AttributeError.

With pyramid-sanity

The redirect is safely URL encoded.

Addendum: Example application

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.httpexceptions import HTTPFound


def redirect(request):
    # Return a redirect to a URL with a non-ASCII character in it.
    return HTTPFound(location="http://example.com/☃")


def hello_world(request):
    return Response(f"Hello World! Query string was: {request.GET}. Form body was: {request.POST}")


if __name__ == "__main__":
    with Configurator() as config:
        config.add_route("redirect", "/redirect")
        config.add_route("hello", "/{anything}")
        config.add_view(hello_world, route_name="hello")
        config.add_view(redirect, route_name="redirect")
        app = config.make_wsgi_app()

    server = make_server("0.0.0.0", 6543, app)
    server.serve_forever()

Attribution

pyramid-sanity was initially based on the solution used by Warehouse's sanity.py, but wraps the fixes up in a Pyramid extension that's easy to add to apps.

The major modifications to this are around the ergonomics:

  • Different errors to allow fine grained handling if you want
  • Configurable checkers and fixers
  • Implicit tweens rather than explicit ones
  • Packaging as a separate package etc.

Hacking

Installing pyramid-sanity in a development environment

You will need

  • Git

  • pyenv Follow the instructions in the pyenv README to install it. The Homebrew method works best on macOS. On Ubuntu follow the Basic GitHub Checkout method.

Clone the git repo

git clone https://github.com/hypothesis/pyramid-sanity.git

This will download the code into a pyramid-sanity directory in your current working directory. You need to be in the pyramid-sanity directory for the rest of the installation process:

cd pyramid-sanity

Run the tests

make test

That's it! You’ve finished setting up your pyramid-sanity development environment. Run make help to see all the commands that're available for linting, code formatting, packaging, etc.

Updating the Cookiecutter scaffolding

This project was created from the https://github.com/hypothesis/h-cookiecutter-pypackage/ template. If h-cookiecutter-pypackage itself has changed since this project was created, and you want to update this project with the latest changes, you can "replay" the cookiecutter over this project. Run:

make template

This will change the files in your working tree, applying the latest updates from the h-cookiecutter-pypackage template. Inspect and test the changes, do any fixups that are needed, and then commit them to git and send a pull request.

If you want make template to skip certain files, never changing them, add these files to "options.disable_replay" in .cookiecutter.json and commit that to git.

If you want make template to update a file that's listed in disable_replay simply delete that file and then run make template, it'll recreate the file for you.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyramid_sanity-1.0.0.tar.gz (9.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyramid_sanity-1.0.0-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file pyramid_sanity-1.0.0.tar.gz.

File metadata

  • Download URL: pyramid_sanity-1.0.0.tar.gz
  • Upload date:
  • Size: 9.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.6.9

File hashes

Hashes for pyramid_sanity-1.0.0.tar.gz
Algorithm Hash digest
SHA256 1122bd2d06a1456e8d1f18be51e59c9094bf5674beea6ab2f5a10e4183febf87
MD5 b3fb9cf25183d31c783ba4b081de1b03
BLAKE2b-256 5c625e3e1b97ffb4d29ed854f16098581aa2dfa890f5b9f88d8931b0b2ed974d

See more details on using hashes here.

File details

Details for the file pyramid_sanity-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: pyramid_sanity-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.5.0.1 requests/2.24.0 setuptools/49.6.0 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.6.9

File hashes

Hashes for pyramid_sanity-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d119d9a28dbda00c25efec3193829e41af7c9b240ec0c546f6ff30bafb19f4fe
MD5 46ff4ba13cbd376302a0e7a2ff9de74b
BLAKE2b-256 48b137f1035bfd221cad22a4a55d13bbb8159f2cf6a39a31fa50ac9cb14a52d0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page