Library / SDK
pinterest/snappass avatar
pinterest/snappass

snappass: self-hosted password links that burn after one view

Share passwords securely

906 stars259 forksPythonMIT

At a glance

What is it?
snappass is a small Flask and Redis service from Pinterest that turns a password into a one-time link, encrypted with Fernet and destroyed once the recipient opens it. It is deliberately minimal, it exposes two HTTP APIs, and it needs a reverse proxy in front of it before it is safe to expose.
Who is it for?
snappass fits teams that want a small internal service for handing one credential to one colleague, on infrastructure they control, without writing the Fernet plus Redis design themselves. It does not fit a shared vault, a credential manager with roles and audit trails, or a deployment that cannot be given a stable hostname, because the link base URL is built from the request Host header unless HOST_OVERRIDE is set.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One password, one link, one look

The problem snappass solves is narrow on purpose. You need to give a single password to a single person, and every channel you have is one you do not control, since email is backed up, chat is logged, and a written note gets lost. The README's framing is that it is a web app that lets you share passwords securely and does one thing. The mechanism of that one thing is the interesting part: if the recipient gets a link to the password and never looks at it, the password goes away, and if they do look at it, the password is revealed. That is a smaller promise than a password manager and a different one. There is no vault, no user account and no shared state to manage, which is exactly why the project can stay small enough to be worth running inside a company that already likes to own its tooling.

The decryption key travels inside the link

Passwords are encrypted using Fernet symmetric encryption from the cryptography library. A random unique key is generated for each password, and it is never stored on the server. Instead it is sent as part of the password link itself. The consequence, which the README calls out directly, is that even if someone gains access to the Redis store, the passwords are still safe, because what Redis holds is ciphertext while the key that can decrypt it lives in the URL handed to the recipient. The stored entry also carries a TTL, and the default is two weeks, 1209600 seconds, which you can override per request. That combination, a key in the link plus a server side expiry, is the whole security design. Anything else you would want from a secret sharing tool, such as an audit trail of who read what, sits outside this model.

Running it locally for the first time

The requirements are Redis and Python 3.8 or newer, and the install is short. The first command pulls the package from PyPI, the second starts the Flask application, and the output tells you it is listening on port 5000 with the reloader enabled.

bash
$ pip install snappass
$ snappass
* Running on http://0.0.0.0:5000/
* Restarting with reloader

Start Redis first, then configure the service through environment variables. SECRET_KEY is the one that matters most, because it is used to sign the key and should be kept secret, and DEBUG turns on the Flask debug server. The Redis connection is set through REDIS_HOST, which defaults to localhost, and REDIS_PORT, which defaults to 6379, with SNAPPASS_REDIS_DB selecting the database and defaulting to db 0. REDIS_URL overrides all three at once if you prefer a single connection string, and REDIS_PREFIX, which defaults to snappass, is prepended to Redis keys to avoid collisions with other clients on the same server. There are also network settings for real deployments: NO_SSL if you are not using TLS, URL_PREFIX when running behind a reverse proxy, and SNAPPASS_BIND_ADDRESS and SNAPPASS_PORT to move the default 0.0.0.0 bind on port 5000.

Two APIs, one for scripts and one for programs

SnapPass exposes two HTTP interfaces, and the difference between them is about lifecycle management. The simple API creates a password and hands back a link, which is what you want inside a script or a CI/CD pipeline where nobody opens a web page. The REST API does the same thing but returns a token and a set of links, so a client can manage the stored password later without parsing HTML from a retrieval page. The simple call is a POST to /api/set_password with a JSON body:

bash
$ curl -X POST -H "Content-Type: application/json"  -d '{"password": "foobar"}' http://localhost:5000/api/set_password/

The response carries the link and the ttl, which is 1209600 by default. Passing an expiration parameter changes the lifetime of the entry:

bash
$ curl -X POST -H "Content-Type: application/json"  -d '{"password": "foobar", "ttl": 3600 }' http://localhost:5000/api/set_password/

The REST equivalent is a POST to /api/v2/passwords, and the README notes the value of that variant for sharing between multiple client applications.

Host header injection is the failure mode to design around

SnapPass builds security sensitive links, and the base URL of those links is the part that can be attacked. When HOST_OVERRIDE is not set, the base URL is derived from the request's Host header, and a client can spoof that header. The project documents two behaviours. Set HOST_OVERRIDE to your canonical hostname in production and it is used for every generated link while the inbound Host header is ignored. If HOST_OVERRIDE is not set, SnapPass derives the base URL only from loopback hosts, meaning localhost, 127.0.0.1 and ::1, and rejects any request with a different Host header with 400 Bad Request. That second behaviour is why the quick start works locally and why a bare container on a shared network is a bad idea. In production you want HOST_OVERRIDE set to a real name such as sub.domain.com, which is what the README recommends when running behind an identity-aware SSO proxy, and you want the service behind a reverse proxy that terminates TLS, since the app itself does not provide SSL.

Docker and compose files that show the intended topology

The repository ships a Dockerfile and a docker-compose.yml, and reading them tells you what the project expects a deployment to look like. The image is built on python:3.8-slim, a non-root user named snappass is created, dependencies are installed from requirements.txt, the translations are compiled with pybabel, the package is installed with setup.py, and the container exposes port 5000 and starts the console script. The compose file defines two services, snappass and redis, with the port mapping 5000:5000, the environment variables REDIS_HOST=redis and NO_SSL=True, and redis:latest as the backing store. Two things stand out for an operator. The pinned dependency versions are older, cryptography 43.0.1, Flask 3.0.0 and redis 5.1.1 among them, so a security conscious deployment will want to review them rather than inherit them. And the compose file sets NO_SSL=True, which is correct for a local topology and wrong for anything reachable from a network, where TLS belongs at the proxy.

A small project, with a release history that has slowed

The licence field on the repository says MIT, which is what most self hosted internal tools want and what makes for easy vendoring into an internal platform. The Python packaging is conventional: setup.py declares the console entry point snappass = snappass.main:main, pulls in Flask, redis and cryptography, and requires Python greater than or equal to 3.8 and less than 4, with classifiers covering 3.8, 3.9 and 3.10. On maintenance, the facts are mixed and worth reading carefully. The repository is not archived and the last push was on 2026-10-08, so work is still landing in the tree. The last published release, v1.6.2, dates from 2024-01-03, and setup.py still carries version 1.6.2, so what you install from PyPI is that release rather than the current tip of the branch. The version marker in setup.py also describes the project as Production/Stable. For an internal tool that is usually enough, but it means the gap between the packaged artifact and the repository is the number to keep in mind when you upgrade.

Editorial conclusion

snappass fits teams that want a small internal service for handing one credential to one colleague, on infrastructure they control, without writing the Fernet plus Redis design themselves. It does not fit a shared vault, a credential manager with roles and audit trails, or a deployment that cannot be given a stable hostname, because the link base URL is built from the request Host header unless HOST_OVERRIDE is set. Before you put it on a network, set HOST_OVERRIDE to your canonical hostname, keep SECRET_KEY somewhere only you can read, and confirm that the Redis instance it points at is not shared with unrelated data, since REDIS_PREFIX is only a naming convention rather than an access control.

Frequently asked questions

How does snappass protect a password stored in Redis?

Passwords are encrypted with Fernet symmetric encryption from the cryptography library, and a random unique key is generated for each password and never stored. The key is sent as part of the password link, so even someone with access to the Redis store sees only ciphertext.

What happens to a password link after it is used?

The password is revealed when the recipient opens the link, and if the link is never opened the password goes away. The stored entry also carries a TTL, two weeks or 1209600 seconds by default, and the simple API accepts a ttl parameter to shorten it.

Do I need to configure HOST_OVERRIDE before running snappass?

For local use you do not, because without HOST_OVERRIDE SnapPass builds link URLs only from loopback hosts and rejects other Host headers with 400 Bad Request. In production the README recommends setting HOST_OVERRIDE to your canonical hostname, so that the inbound Host header is ignored and links cannot be pointed elsewhere.

Official sources

  1. Issues
  2. License: MIT
  3. pinterest/snappass on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pinterest-snappass.svg)](https://hysenlabs.com/projects/pinterest-snappass)

Was this useful?