Merge pull request 'Fix security headers, ARIA, cleanup, health check, docker-compose' (#39) from fix/security-and-ope into main

This commit is contained in:
Jarian Cottingham 2026-07-04 23:32:23 -05:00
commit 9ff745f3dd
3 changed files with 19 additions and 22 deletions

24
app.py
View File

@ -27,7 +27,7 @@ ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff'}
ALLOWED_IMAGE_MAGIC = { ALLOWED_IMAGE_MAGIC = {
'png': b'\x89PNG\r\n\x1a\n', 'png': b'\x89PNG\r\n\x1a\n',
'jpg': b'\xff\xd8\xff', 'jpg': b'\xff\xd8\xff',
'gif': b'GIF87a', b'GIF89a', 'gif': (b'GIF87a', b'GIF89a'),
'webp': b'RIFF', 'webp': b'RIFF',
'bmp': b'BM', 'bmp': b'BM',
} }
@ -39,14 +39,6 @@ _UPLOAD_WINDOW = 60
_csrf_secret = secrets.token_hex(32) _csrf_secret = secrets.token_hex(32)
EXPIRY_OPTIONS = [
('1h', '1 hour'),
('1d', '1 day'),
('1w', '1 week'),
('1m', '1 month'),
('forever', 'Never'),
]
ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'tiff'} ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg', 'tiff'}
ALLOWED_TEXT_EXTENSIONS = {'txt', 'py', 'js', 'ts', 'c', 'cpp', 'h', 'java', 'rb', 'go', 'rs', 'md', 'json', 'xml', 'yaml', 'yml', 'html', 'css', 'sh', 'log', 'csv', 'sql', 'ini', 'cfg', 'toml', 'lua', 'php', 'swift', 'kt', 'scala', 'r', 'pl', 'hs', 'zig', 'nix'} ALLOWED_TEXT_EXTENSIONS = {'txt', 'py', 'js', 'ts', 'c', 'cpp', 'h', 'java', 'rb', 'go', 'rs', 'md', 'json', 'xml', 'yaml', 'yml', 'html', 'css', 'sh', 'log', 'csv', 'sql', 'ini', 'cfg', 'toml', 'lua', 'php', 'swift', 'kt', 'scala', 'r', 'pl', 'hs', 'zig', 'nix'}
@ -166,10 +158,20 @@ def save_text_content(paste_id, content):
f.write(content) f.write(content)
@app.before_request # Fix #16 - ensure_dirs at startup only, not every request
def before_request():
ensure_dirs() ensure_dirs()
# Fix #1 - scheduled cleanup of expired pastes
def _cleanup_loop():
while True:
time.sleep(300)
try:
cleanup_expired()
except Exception as e:
print(f"Cleanup error: {e}")
threading.Thread(target=_cleanup_loop, daemon=True).start()
@app.after_request @app.after_request
def add_security_headers(response): def add_security_headers(response):

View File

@ -1,5 +1,3 @@
version: '3.8'
services: services:
pastebin: pastebin:
build: . build: .
@ -12,7 +10,3 @@ services:
- PORT=8080 - PORT=8080
- SECRET_KEY=${SECRET_KEY:-change-me-to-a-random-secret} - SECRET_KEY=${SECRET_KEY:-change-me-to-a-random-secret}
restart: unless-stopped restart: unless-stopped
volumes:
uploads-data:
store-data:

View File

@ -4,10 +4,11 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PasteBin</title> <title>PasteBin</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E📋%3C/text%3E%3C/svg%3E">
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
</head> </head>
<body> <body>
<div class="container"> <div class="container" role="main">
<header> <header>
<h1><a href="/">PasteBin</a></h1> <h1><a href="/">PasteBin</a></h1>
<p>Share text, images, and files temporarily</p> <p>Share text, images, and files temporarily</p>
@ -18,9 +19,9 @@
{% endif %} {% endif %}
<div class="tabs"> <div class="tabs">
<button class="tab active" data-tab="text">Text</button> <button class="tab active" data-tab="text" aria-label="Text paste tab" aria-selected="true">Text</button>
<button class="tab" data-tab="image">Image</button> <button class="tab" data-tab="image" aria-label="Image paste tab" aria-selected="false">Image</button>
<button class="tab" data-tab="file">File</button> <button class="tab" data-tab="file" aria-label="File paste tab" aria-selected="false">File</button>
</div> </div>
<form method="POST" action="/paste" enctype="multipart/form-data" id="uploadForm"> <form method="POST" action="/paste" enctype="multipart/form-data" id="uploadForm">