From 9aeed2af50109fa03823cc0202be72a699001a6e Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sat, 16 May 2026 12:20:01 +0100 Subject: [PATCH] security: remove config.json from tracking, add example configs and nginx guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config.json now in .gitignore — it contained admin password hash and secret_key placeholder which should never be in version control - Add config.example.json as a safe, secrets-free template for new deployments - Add nginx.conf.example with proper reverse-proxy guards: - Static files served by Nginx directly (not Flask) - Explicit deny on .py, .json, .db, .log, .env and other sensitive extensions - Block dot-files and __pycache__ paths - No directory listing (autoindex off) - HTTPS enforcement with HSTS --- .gitignore | 8 ++- config.json => config.example.json | 15 +++-- nginx.conf.example | 104 +++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 7 deletions(-) rename config.json => config.example.json (87%) create mode 100644 nginx.conf.example diff --git a/.gitignore b/.gitignore index 4a4b29e..825eeef 100644 --- a/.gitignore +++ b/.gitignore @@ -60,5 +60,9 @@ Thumbs.db *.tmp *.temp -# User uploads (if you add file upload functionality) -uploads/gunicorn.pid +# Gunicorn +gunicorn.pid + +# Runtime config — contains secrets (SECRET_KEY, admin password hash). +# Copy config.example.json → config.json and fill in your values. +config.json diff --git a/config.json b/config.example.json similarity index 87% rename from config.json rename to config.example.json index 325e29e..47c0a1c 100644 --- a/config.json +++ b/config.example.json @@ -1,5 +1,5 @@ { - "_comment": "Bastebin configuration. Restart the server after changing values.", + "_comment": "Bastebin configuration. Copy this file to config.json, fill in your values, and restart the server.", "site": { "name": "Bastebin", @@ -13,11 +13,13 @@ "host": "0.0.0.0", "port": 5500, "debug": false, - "secret_key": "ce81867ddcc16729b42d7ae0564140e6bcc83bcf5dbbe77b7e5b6c5aa4199347" + "use_https": true, + "_secret_key_note": "Prefer the SECRET_KEY environment variable. This fallback is used only if the env var is not set.", + "secret_key": "CHANGE-ME-use-a-long-random-string-at-least-32-chars" }, "database": { - "path": "pastebin.db" + "path": "bastebin.db" }, "pastes": { @@ -133,9 +135,12 @@ {"value": "toml", "name": "TOML"}, {"value": "ini", "name": "INI / Config"} ], + "admin": { "enabled": true, - "user": "admin", - "pass": "scrypt:32768:8:1$WKz9I6qE4hh0paUQ$6fd34e7f0195280f81301a92f5bac26d247f95d64744cb2c6e44108a3d8420eba5343b7b2ba657f39404f4ef102ce2e62a689e7797a43f3169fd69dca7b5b3c7" + "_user_note": "Set a strong username — not 'admin'.", + "user": "your-admin-username", + "_pass_note": "Generate with: python generate_hash.py yourpassword", + "pass": "REPLACE-WITH-SCRYPT-HASH-FROM-generate_hash.py" } } diff --git a/nginx.conf.example b/nginx.conf.example new file mode 100644 index 0000000..6421caf --- /dev/null +++ b/nginx.conf.example @@ -0,0 +1,104 @@ +# ── Bastebin — Nginx reverse-proxy configuration ───────────────────────────── +# +# Copy to /etc/nginx/sites-available/bastebin and symlink: +# sudo ln -s /etc/nginx/sites-available/bastebin /etc/nginx/sites-enabled/ +# sudo nginx -t && sudo systemctl reload nginx +# +# Assumes: +# - Gunicorn is bound to a Unix socket at /run/bastebin/gunicorn.sock +# - Your TLS certificates are managed by Certbot / Let's Encrypt +# - Static files live at /path/to/bastebin/static/ +# +# Replace every occurrence of: +# bastebin.com → your actual domain +# /path/to/bastebin → absolute path to your project directory + +server { + listen 80; + listen [::]:80; + server_name bastebin.com www.bastebin.com; + + # Redirect all HTTP → HTTPS + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name bastebin.com www.bastebin.com; + + # ── TLS (managed by Certbot — do not edit manually) ────────────────────── + ssl_certificate /etc/letsencrypt/live/bastebin.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/bastebin.com/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + # ── Security headers (supplement Flask's own headers) ──────────────────── + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + add_header X-Robots-Tag "noindex, nofollow" always; + + # ── Block access to sensitive paths ────────────────────────────────────── + # These should NEVER be served, even accidentally. + location ~* \.(py|json|db|sqlite|sqlite3|log|cfg|ini|toml|env|sh|conf|bak|orig)$ { + deny all; + return 404; + } + + # Block dot-files / hidden files (e.g. .env, .git) + location ~ /\. { + deny all; + return 404; + } + + # Block __pycache__ and similar Python internals + location ~* /__pycache__/ { + deny all; + return 404; + } + + # ── Static files served directly by Nginx (fast path) ─────────────────── + # Nginx serves these without hitting Gunicorn at all. + location /static/ { + alias /path/to/bastebin/static/; + + # No directory listings + autoindex off; + + # Cache busting — serve with long expiry; filename includes hash in prod + expires 7d; + add_header Cache-Control "public, max-age=604800, immutable"; + + # Only allow known file extensions; block anything else + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + # Passthrough — let the alias serve it + } + location / { + # Anything under /static/ that doesn't match above is denied + deny all; + return 404; + } + } + + # ── Gunicorn (Flask application) ───────────────────────────────────────── + location / { + proxy_pass http://unix:/run/bastebin/gunicorn.sock; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout 10s; + proxy_read_timeout 60s; + proxy_send_timeout 60s; + + # Body size limit — match config.json max_size_bytes (2 MB) + overhead + client_max_body_size 3m; + } + + # ── Logging ─────────────────────────────────────────────────────────────── + access_log /var/log/nginx/bastebin.access.log; + error_log /var/log/nginx/bastebin.error.log warn; +}