# FlaskPaste A lightweight, secure pastebin REST API built with Flask. ## Features - **Simple REST API** - Create, retrieve, list, and delete pastes via HTTP - **Binary support** - Upload text, images, archives, and other binary content - **Automatic MIME detection** - Magic byte detection (images, video, audio, documents, executables, archives) - **Client certificate authentication** - mTLS or header-based via reverse proxy - **Tiered expiry** - 1 day (anon), 7 days (untrusted), 30 days (trusted PKI) - **Size limits** - 3 MiB anonymous, 50 MiB authenticated - **Abuse prevention** - Content-hash deduplication throttles spam - **Proof-of-work** - Computational puzzles prevent automated abuse - **Anti-flood** - Dynamic PoW difficulty increases under attack - **Rate limiting** - Per-IP throttling with X-RateLimit-* headers - **E2E encryption** - Client-side AES-256-GCM with key in URL fragment - **Burn-after-read** - Single-access pastes that auto-delete - **Password protection** - PBKDF2-HMAC-SHA256 with 600k iterations - **Built-in PKI** - Certificate authority for client certificate issuance - **Admin support** - First registered user can manage all pastes - **Security headers** - HSTS, CSP, X-Frame-Options, X-Content-Type-Options - **CLI client** - Standalone `fpaste` tool with encryption support - **Request tracing** - X-Request-ID for log correlation - **URL shortener** - `/s/` endpoints for creating, resolving, and managing short URLs - **Audit logging** - PKI certificate lifecycle events (issue, revoke, auth failure) - **Observability** - Request duration metrics via Prometheus histogram - **Minimal dependencies** - Flask + SQLite, optional cryptography for CLI ## Quick Start ```bash # Clone and setup git clone cd flaskpaste python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # Run development server python run.py ``` ## API Endpoints | Method | Endpoint | Description | |--------|----------|-------------| | `GET /` | API information and usage | | `GET /health` | Health check (returns DB status) | | `GET /challenge` | Get proof-of-work challenge | | `GET /client` | Download fpaste CLI client | | `POST /` | Create a new paste | | `GET /` | Retrieve paste metadata | | `HEAD /` | Retrieve paste metadata (headers only) | | `GET //raw` | Retrieve raw paste content | | `HEAD //raw` | Retrieve paste headers (no body) | | `DELETE /` | Delete paste (requires auth) | | `GET /pastes` | List user's pastes (requires auth) | | `GET /register/challenge` | Get PoW challenge for registration | | `POST /register` | Register and get client certificate | | `POST /s` | Create short URL (PoW required) | | `GET /s` | List your short URLs (requires auth) | | `GET /s/` | Redirect to target URL (302) | | `GET /s//info` | Short URL metadata | | `DELETE /s/` | Delete short URL (requires auth) | | `GET /pki` | PKI status and CA info | | `GET /pki/ca.crt` | Download CA certificate | ## Usage Examples ### Create a paste (raw text) ```bash curl --data-binary @file.txt http://localhost:5000/ ``` ### Create a paste (piped input) ```bash echo "Hello, World!" | curl --data-binary @- http://localhost:5000/ ``` ### Create a paste (JSON) ```bash curl -H "Content-Type: application/json" \ -d '{"content":"Hello from JSON!"}' \ http://localhost:5000/ ``` ### Retrieve paste metadata ```bash curl http://localhost:5000/abc12345 ``` ### Retrieve raw content ```bash curl http://localhost:5000/abc12345/raw ``` ### Delete a paste (requires authentication) ```bash curl -X DELETE \ -H "X-SSL-Client-SHA1: " \ http://localhost:5000/abc12345 ``` ### List user's pastes (requires authentication) ```bash curl -H "X-SSL-Client-SHA1: " \ http://localhost:5000/pastes ``` ### Create a short URL ```bash curl -X POST -H "Content-Type: application/json" \ -d '{"url":"https://example.com/long/path"}' \ http://localhost:5000/s ``` ### Follow a short URL ```bash curl -L http://localhost:5000/s/AbCdEfGh ``` ## CLI Client A standalone command-line client `fpaste` is included. For E2E encryption, install the optional `cryptography` package. ### Installation ```bash # Download from running server curl -sS https://paste.example.com/client > fpaste && chmod +x fpaste # Optional: enable encryption support pip install cryptography ``` ### Basic Usage ```bash # Create paste from file (encrypts by default) ./fpaste file.txt # Returns: https://paste.example.com/abc123# # Shortcut: file path auto-selects "create" command ./fpaste secret.txt # Same as: ./fpaste create secret.txt # Create paste from stdin echo "Hello" | ./fpaste # Disable encryption (upload plaintext) ./fpaste -E file.txt ./fpaste create --no-encrypt file.txt # Create burn-after-read paste (single access, auto-deletes) ./fpaste -b secret.txt # Create paste with custom expiry (1 hour) ./fpaste -x 3600 temp.txt # Combine options: encrypted + burn-after-read ./fpaste -b secret.txt # Get paste content (auto-decrypts if URL has #key fragment) ./fpaste get "https://paste.example.com/abc123#" # Get paste metadata ./fpaste get -m abc12345 # List your pastes (requires auth) ./fpaste list # List all pastes (admin only) ./fpaste list --all # Delete paste (requires auth) ./fpaste delete abc12345 # Delete multiple pastes ./fpaste delete abc12345 def67890 # Delete all pastes (admin only, requires confirmation) ./fpaste delete --all --confirm 42 # where 42 is expected count # Show server info ./fpaste info # Register and get client certificate (if server supports it) ./fpaste register # Saves certificate to ~/.config/fpaste/ # Register with auto-configuration ./fpaste register --configure # Creates cert files and updates config with fingerprint # Register with custom name ./fpaste register -n my-laptop --configure ``` ### End-to-End Encryption Content is encrypted by default using AES-256-GCM before upload: - Key is generated locally and never sent to server - Key is appended to URL as fragment (`#...`) which browsers never transmit - Server stores only opaque ciphertext - Retrieval auto-detects `#key` fragment and decrypts locally - Use `-E` or `--no-encrypt` to disable encryption This provides true zero-knowledge storage: the server cannot read your content. ### Configuration Set server URL and authentication via environment or config file: ```bash # Environment variables export FLASKPASTE_SERVER="https://paste.example.com" export FLASKPASTE_CERT_SHA1="your-cert-fingerprint" # Or config file (~/.config/fpaste/config) server = https://paste.example.com cert_sha1 = your-cert-fingerprint ``` ### mTLS Client Certificate Authentication The CLI supports mutual TLS (mTLS) for direct client certificate authentication: ```bash # Environment variables export FLASKPASTE_CLIENT_CERT="/path/to/client.crt" export FLASKPASTE_CLIENT_KEY="/path/to/client.key" export FLASKPASTE_CA_CERT="/path/to/ca.crt" # Optional CA verification # Or config file (~/.config/fpaste/config) client_cert = /path/to/client.crt client_key = /path/to/client.key ca_cert = /path/to/ca.crt ``` When client certificates are configured, fpaste performs mTLS authentication directly with the server (or TLS-terminating proxy). This is more secure than header-based authentication as the certificate is validated at the TLS layer. ### Generating Client Certificates The CLI includes a built-in certificate generator for mTLS authentication: ```bash # Generate EC certificate (recommended, fast key generation) ./fpaste cert # Outputs fingerprint to stdout, details to stderr # Generate and auto-configure fpaste ./fpaste cert --configure # Creates ~/.config/fpaste/client.{crt,key} and updates config # Custom options ./fpaste cert -a rsa -b 4096 -d 730 -n "my-client" --configure # RSA 4096-bit key, 2-year validity, custom common name # Supported curves for EC: secp256r1, secp384r1 (default), secp521r1 ./fpaste cert -c secp256r1 --configure ``` The generated certificate includes: - `CLIENT_AUTH` extended key usage for mTLS - SHA1 fingerprint compatible with `X-SSL-Client-SHA1` header - Self-signed with configurable validity period ## Configuration Configuration via environment variables: | Variable | Default | Description | |----------|---------|-------------| | `FLASK_DEBUG` | `0` | Enable debug mode (`1` = enabled) | | `FLASKPASTE_DB` | `./data/pastes.db` | SQLite database path | | `FLASKPASTE_ID_LENGTH` | `12` | Paste ID length (hex characters) | | `FLASKPASTE_MAX_ANON` | `3145728` (3 MiB) | Max paste size for anonymous users | | `FLASKPASTE_MAX_AUTH` | `52428800` (50 MiB) | Max paste size for authenticated users | | `FLASKPASTE_EXPIRY_ANON` | `86400` (1 day) | Default expiry for anonymous users | | `FLASKPASTE_EXPIRY_UNTRUSTED` | `604800` (7 days) | Default expiry for untrusted cert users | | `FLASKPASTE_EXPIRY_TRUSTED` | `2592000` (30 days) | Default expiry for trusted (PKI) cert users | | `FLASKPASTE_MAX_EXPIRY` | `7776000` (90 days) | Maximum custom expiry allowed | | `FLASKPASTE_DEDUP_WINDOW` | `3600` (1 hour) | Dedup throttle window in seconds | | `FLASKPASTE_DEDUP_MAX` | `3` | Max identical submissions per window | | `FLASKPASTE_PROXY_SECRET` | (empty) | Shared secret for proxy trust validation | | `FLASKPASTE_POW_DIFFICULTY` | `20` | PoW difficulty (leading zero bits, 0=disabled) | | `FLASKPASTE_POW_TTL` | `300` (5 min) | PoW challenge validity period | | `FLASKPASTE_REGISTER_POW` | `24` | Registration PoW difficulty (higher than paste creation) | | `FLASKPASTE_POW_SECRET` | (auto) | Secret for signing PoW challenges | | `FLASKPASTE_ANTIFLOOD` | `1` | Enable anti-flood (dynamic PoW difficulty) | | `FLASKPASTE_ANTIFLOOD_WINDOW` | `60` | Anti-flood measurement window (seconds) | | `FLASKPASTE_ANTIFLOOD_THRESHOLD` | `5` | Requests per window before difficulty increase | | `FLASKPASTE_ANTIFLOOD_STEP` | `2` | Difficulty bits added per threshold breach | | `FLASKPASTE_ANTIFLOOD_MAX` | `28` | Maximum PoW difficulty | | `FLASKPASTE_ANTIFLOOD_DECAY` | `60` | Seconds before difficulty decreases | | `FLASKPASTE_RATE_LIMIT` | `1` | Enable IP-based rate limiting | | `FLASKPASTE_RATE_WINDOW` | `60` | Rate limit window (seconds) | | `FLASKPASTE_RATE_MAX` | `10` | Max requests per window (anon) | | `FLASKPASTE_RATE_AUTH_MULT` | `5` | Multiplier for authenticated users | | `FLASKPASTE_SHORT_ID_LENGTH` | `8` | Short URL ID length (base62 characters) | | `FLASKPASTE_SHORT_URL_MAX` | `2048` | Maximum target URL length | | `FLASKPASTE_URL_PREFIX` | (empty) | URL prefix for reverse proxy deployments | | `FLASKPASTE_MIN_ENTROPY` | `0` | Min entropy bits/byte (0=disabled, 6.0=require encryption) | | `FLASKPASTE_MIN_ENTROPY_SIZE` | `256` | Only check entropy for content >= this size | | `FLASKPASTE_PKI_ENABLED` | `0` | Enable PKI certificate authority | | `FLASKPASTE_PKI_CA_PASSWORD` | (empty) | CA password (required when PKI enabled) | | `FLASKPASTE_PKI_CERT_DAYS` | `365` | Client certificate validity (days) | ## Authentication FlaskPaste uses client certificate authentication. When deployed behind a reverse proxy (nginx, Apache), configure the proxy to: 1. Terminate TLS and validate client certificates 2. Extract the certificate SHA1 fingerprint 3. Pass it via the `X-SSL-Client-SHA1` header Example nginx configuration: ```nginx location / { proxy_pass http://127.0.0.1:5000; proxy_set_header X-SSL-Client-SHA1 $ssl_client_fingerprint; } ``` ### Trust Levels FlaskPaste distinguishes three trust levels: | Level | Description | Default Expiry | |-------|-------------|----------------| | Anonymous | No certificate | 1 day | | Untrusted | Valid certificate, not registered via PKI | 7 days | | Trusted | Certificate registered via `/register` endpoint | 30 days | Authenticated users can: - Upload larger pastes (50 MiB vs 3 MiB) - Delete their own pastes - List their own pastes **Admin users** (first user to register via PKI) can additionally: - List all pastes (`GET /pastes?all=1`) - Delete any paste ## Production Deployment ### Using Gunicorn ```bash pip install gunicorn gunicorn -w 4 -b 0.0.0.0:5000 wsgi:app ``` ### Using Podman/Docker ```bash podman build -t flaskpaste . podman run -d -p 5000:5000 -v flaskpaste-data:/app/data flaskpaste ``` See `Containerfile` for container build configuration. ### Using Podman Quadlet Quadlet integrates rootless containers with systemd. Deploy as a dedicated user: ```bash # Create service user and data directory useradd -r -m -d /home/flaskpaste -s /sbin/nologin flaskpaste mkdir -p /opt/flaskpaste chown flaskpaste:flaskpaste /opt/flaskpaste # Build image as flaskpaste user sudo -u flaskpaste podman build -t localhost/flaskpaste:latest . # Install Quadlet unit mkdir -p /home/flaskpaste/.config/containers/systemd cp flaskpaste.container /home/flaskpaste/.config/containers/systemd/ chown -R flaskpaste:flaskpaste /home/flaskpaste/.config # Enable lingering (start user services at boot) loginctl enable-linger flaskpaste # Start service systemctl --user -M flaskpaste@ daemon-reload systemctl --user -M flaskpaste@ start flaskpaste ``` Manage with systemctl: ```bash systemctl --user -M flaskpaste@ status flaskpaste systemctl --user -M flaskpaste@ restart flaskpaste journalctl --user -M flaskpaste@ -u flaskpaste ``` ### Using systemd ```bash # Create service user sudo useradd -r -s /sbin/nologin flaskpaste # Copy application sudo mkdir -p /opt/flaskpaste/data sudo cp -r . /opt/flaskpaste/ sudo chown -R flaskpaste:flaskpaste /opt/flaskpaste # Copy service unit and environment file sudo cp examples/flaskpaste.service /etc/systemd/system/ sudo mkdir -p /etc/flaskpaste sudo cp examples/flaskpaste.env /etc/flaskpaste/env sudo chmod 600 /etc/flaskpaste/env # Enable and start service sudo systemctl daemon-reload sudo systemctl enable --now flaskpaste ``` See `examples/` for service unit and configuration templates. ## Development ### Running Tests ```bash pip install pytest pytest-cov pytest tests/ -v ``` ### Test Coverage ```bash pytest tests/ --cov=app --cov-report=term-missing ``` ### Project Structure ``` flaskpaste/ ├── app/ │ ├── __init__.py # Flask app factory │ ├── config.py # Configuration classes │ ├── database.py # SQLite management │ ├── audit.py # Audit logging for PKI events │ ├── metrics.py # Prometheus metrics and histograms │ └── api/ │ ├── __init__.py # Blueprint setup │ └── routes.py # API endpoints ├── tests/ # Test suite (346+ tests) ├── data/ # SQLite database ├── run.py # Development server ├── wsgi.py # Production WSGI entry ├── Containerfile # Podman/Docker build ├── compose.yaml # Podman/Docker Compose ├── flaskpaste.container # Podman Quadlet unit └── requirements.txt # Dependencies ``` ## Security Considerations - **Input validation** - Paste IDs are hex-only, auth headers validated - **MIME sanitization** - Content-Type headers are sanitized - **SQL injection protection** - Parameterized queries throughout - **Ownership enforcement** - Only owners (or admins) can delete pastes - **Size limits** - Prevents resource exhaustion attacks - **Abuse prevention** - Content-hash deduplication prevents spam flooding - **Entropy enforcement** - Optional minimum entropy rejects plaintext uploads - **E2E encryption** - Client-side AES-256-GCM, server is zero-knowledge - **Burn-after-read** - Single-use pastes for sensitive data - **Password protection** - PBKDF2-HMAC-SHA256 with 600k iterations - **Security headers** - HSTS, CSP, X-Frame-Options, X-Content-Type-Options - **Proof-of-work** - Computational puzzles prevent automated spam - **Rate limiting** - Per-IP throttling with X-RateLimit-* headers - **Open redirect prevention** - URL shortener allows only http/https schemes with valid host - **Request tracing** - X-Request-ID for log correlation - **PKI support** - Built-in CA for client certificate issuance - **Audit logging** - PKI certificate events for compliance and forensics - **Observability** - Prometheus metrics for monitoring and alerting ## License MIT