From 7deba711d45154745ab0c4d06cc582ea9642fe56 Mon Sep 17 00:00:00 2001 From: Username Date: Sat, 20 Dec 2025 08:48:13 +0100 Subject: [PATCH] entropy: exempt small content from check Small data has unreliable entropy measurement due to sample size. MIN_ENTROPY_SIZE (default 256 bytes) sets the threshold. --- app/api/routes.py | 3 ++- app/config.py | 4 +++- documentation/api.md | 5 +++-- tests/test_abuse_prevention.py | 17 +++++++++++++++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/api/routes.py b/app/api/routes.py index 71f6952..24cc73b 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -455,7 +455,8 @@ def create_paste(): # Check minimum entropy requirement (encryption enforcement) min_entropy = current_app.config.get("MIN_ENTROPY", 0) - if min_entropy > 0: + min_entropy_size = current_app.config.get("MIN_ENTROPY_SIZE", 256) + if min_entropy > 0 and content_size >= min_entropy_size: entropy = _calculate_entropy(content) if entropy < min_entropy: current_app.logger.warning( diff --git a/app/config.py b/app/config.py index f0f1089..5f418bb 100644 --- a/app/config.py +++ b/app/config.py @@ -29,8 +29,10 @@ class Config: # Minimum entropy requirement (0 = disabled) # Encrypted data has ~7.5-8.0 bits/byte, plaintext ~4.0-5.0 - # Set to 7.0+ to effectively require encryption + # Set to 6.0+ to effectively require encryption MIN_ENTROPY = float(os.environ.get("FLASKPASTE_MIN_ENTROPY", 0)) + # Minimum size for entropy check (small data has unreliable entropy measurement) + MIN_ENTROPY_SIZE = int(os.environ.get("FLASKPASTE_MIN_ENTROPY_SIZE", 256)) # Reverse proxy trust configuration # SECURITY: The X-SSL-Client-SHA1 header is trusted for authentication. diff --git a/documentation/api.md b/documentation/api.md index 209272b..9b813f0 100644 --- a/documentation/api.md +++ b/documentation/api.md @@ -355,7 +355,8 @@ FlaskPaste can require minimum content entropy to enforce client-side encryption **Configuration:** ```bash -export FLASKPASTE_MIN_ENTROPY=7.0 # Require ~encryption-level entropy (0=disabled) +export FLASKPASTE_MIN_ENTROPY=6.0 # Require encryption-level entropy (0=disabled) +export FLASKPASTE_MIN_ENTROPY_SIZE=256 # Only check content >= this size (default: 256) ``` **Response (400 Bad Request):** @@ -369,7 +370,7 @@ export FLASKPASTE_MIN_ENTROPY=7.0 # Require ~encryption-level entropy (0=disabl ``` **Caveats:** -- Small data (<256 bytes) has naturally lower measured entropy even when encrypted +- Small data is exempt (configurable via `MIN_ENTROPY_SIZE`, default 256 bytes) - Compressed data (gzip, zip) also has high entropy — not distinguishable from encrypted - This is a heuristic, not cryptographic proof of encryption diff --git a/tests/test_abuse_prevention.py b/tests/test_abuse_prevention.py index f99e2e8..7a0a7b7 100644 --- a/tests/test_abuse_prevention.py +++ b/tests/test_abuse_prevention.py @@ -250,9 +250,11 @@ class TestEntropyEnforcement: def test_plaintext_rejected(self, entropy_client): """Plaintext content should be rejected when entropy required.""" + # Must be >= MIN_ENTROPY_SIZE (256 bytes) to trigger check + plaintext = b"Hello, this is plain English text. " * 10 # ~350 bytes response = entropy_client.post( "/", - data=b"Hello, this is plain English text with low entropy.", + data=plaintext, content_type="text/plain", ) assert response.status_code == 400 @@ -287,12 +289,23 @@ class TestEntropyEnforcement: def test_repeated_bytes_rejected(self, entropy_client): """Repeated bytes have zero entropy and should be rejected.""" + # Must be >= MIN_ENTROPY_SIZE (256 bytes) to trigger check response = entropy_client.post( "/", - data=b"a" * 1000, + data=b"a" * 500, content_type="text/plain", ) assert response.status_code == 400 data = response.get_json() assert data["entropy"] == 0.0 + + def test_small_content_exempt(self, entropy_client): + """Small content should be exempt from entropy check.""" + # Content < MIN_ENTROPY_SIZE (256 bytes) should pass + response = entropy_client.post( + "/", + data=b"Small plaintext content", + content_type="text/plain", + ) + assert response.status_code == 201