
A filename extension is not a trust boundary
A visitor-supplied filename and browser-declared content type are not sufficient evidence that an upload is safe to process. A small compressed file can also describe a very large pixel grid. Start by defining the formats and dimensions the product genuinely needs. This tutorial narrows avatar uploads to static images and tests a validation core independently of a web framework, making the boundary understandable before connecting it to a public endpoint.
The helper bounds bytes before decoding, identifies the actual supported format, checks dimensions and rejects animation. It then loads and re-encodes the image into a controlled output. SVG and other formats are outside this particular contract. These restrictions do not eliminate every decoder risk; maintain the processing library and apply suitable memory and execution budgets. A successful format check should not be treated as permission to process unlimited work on an unprotected request path.
Bound input and decode a controlled image
The application generates its own filename and does not derive storage paths from the visitor's original name. The destination is private, and exclusive creation avoids replacing an existing output. Format and dimensions are constrained, while real directory permissions and response serving remain deployment responsibilities. On Windows, Python permission modes do not substitute for reviewing actual directory ACLs. Keep the file-processing contract distinct from the operating-system controls protecting the resulting content.
Save the four files and install Pillow in a project virtual environment. Run python -m unittest test_avatar_store.py to exercise genuine image bytes, generated names, resizing, invalid content and byte-limit rejection. Then run python demo_upload.py. It creates an in-memory fixture and prints the resulting WebP filename, without collecting a real visitor upload or retrieving a remote URL. The fixture is intentionally local so that the basic checks remain reproducible and bounded.
This function is not a complete web endpoint. Authentication, authorization, request protections, upload rates, storage quotas and operational logging belong in the application layer. Serve the correct content type and appropriate anti-sniffing headers, considering a separate user-content origin where the product requires one. A successful re-encoding test does not justify removing those controls and does not guarantee that every possible malicious input or downstream serving configuration has been assessed.
Complete executable files
The four files are complete. The article provides prerequisites, command order and verification; code is neither shortened nor hidden.
1. Complete policy.json
Save this entire file beside the others using the exact filename.
{"maxBytes": 1000000, "maxPixels": 4000000, "maxWidth": 1000}The execution section explains this file’s command, expected outcome and limits.
2. Complete avatar_store.py
Save this entire file beside the others using the exact filename.
from io import BytesIO
from pathlib import Path
import uuid
from PIL import Image, ImageOps
def store_avatar(stream, directory, policy):
data = stream.read(policy['maxBytes'] + 1)
if len(data) > policy['maxBytes']:
raise ValueError('Upload exceeds byte limit')
with Image.open(BytesIO(data)) as original:
if original.format not in {'JPEG', 'PNG'} or getattr(original, 'is_animated', False):
raise ValueError('Only single JPEG or PNG images are accepted')
if original.width * original.height > policy['maxPixels']:
raise ValueError('Image exceeds pixel limit')
original.load()
image = ImageOps.exif_transpose(original).convert('RGB')
image.thumbnail((policy['maxWidth'], policy['maxWidth']))
root = Path(directory); root.mkdir(mode=0o700, parents=True, exist_ok=True)
target = root / (uuid.uuid4().hex + '.webp')
with target.open('xb') as output:
image.save(output, format='WEBP', quality=85)
return target.nameThe execution section explains this file’s command, expected outcome and limits.
3. Complete demo_upload.py
Save this entire file beside the others using the exact filename.
from io import BytesIO
import json
from pathlib import Path
from PIL import Image
from avatar_store import store_avatar
if __name__ == '__main__':
policy = json.loads(Path(__file__).with_name('policy.json').read_text())
incoming = BytesIO(); Image.new('RGB', (600, 300), 'green').save(incoming, format='PNG'); incoming.seek(0)
print(store_avatar(incoming, 'private-avatars', policy))The execution section explains this file’s command, expected outcome and limits.
4. Complete test_avatar_store.py
Save this entire file beside the others using the exact filename.
from io import BytesIO
from pathlib import Path
import tempfile
import unittest
from PIL import Image, UnidentifiedImageError
from avatar_store import store_avatar
class UploadTests(unittest.TestCase):
def test_reencode_and_generated_name(self):
with tempfile.TemporaryDirectory() as directory:
incoming = BytesIO(); Image.new('RGB', (60, 30)).save(incoming, format='PNG'); incoming.seek(0)
name = store_avatar(incoming, directory, {'maxBytes': 10000, 'maxPixels': 10000, 'maxWidth': 40})
self.assertRegex(name, r'^[a-f0-9]{32}\.webp$')
with Image.open(Path(directory) / name) as image: self.assertEqual(image.size, (40, 20))
def test_invalid_bytes_and_size(self):
with tempfile.TemporaryDirectory() as directory:
policy = {'maxBytes': 10, 'maxPixels': 10000, 'maxWidth': 40}
with self.assertRaises(ValueError): store_avatar(BytesIO(b'x' * 11), directory, policy)
with self.assertRaises(UnidentifiedImageError): store_avatar(BytesIO(b'fake.jpg'), directory, policy)
if __name__ == '__main__': unittest.main()The execution section explains this file’s command, expected outcome and limits.
Run the example and retain service-level controls
For an actual service, also test oversized dimensions, truncated images, animation, disk failures and concurrent requests. Validation errors should be actionable without exposing private content or internal server paths. Tune the limits to avatar requirements; document or video uploads need different contracts. The practical goal is a small, explainable path from bounded input to a verified output that works alongside the service's broader controls, rather than a single function advertised as universal upload security.
Implementation checklist
- Bound both bytes and decoded pixels.
- Generate names independently of input paths.
- Check real format and reject animation.
- Keep authorization and request controls in the service.
References
Editorial guidance: Liyan Knowledge.




