add entropy enforcement for optional encryption requirement

Shannon entropy check rejects low-entropy content when MIN_ENTROPY > 0.
Encrypted data ~7.5-8.0 bits/byte, plaintext ~4.0-5.0 bits/byte.
Configurable via FLASKPASTE_MIN_ENTROPY environment variable.
This commit is contained in:
Username
2025-12-20 06:57:50 +01:00
parent 9ccd4225dd
commit 8addf2d9e8
4 changed files with 154 additions and 0 deletions

View File

@@ -3,6 +3,7 @@
import hashlib
import hmac
import json
import math
import os
import re
import secrets
@@ -58,6 +59,33 @@ MAGIC_SIGNATURES = {
}
def _calculate_entropy(data: bytes) -> float:
"""Calculate Shannon entropy in bits per byte.
Returns value between 0 (uniform) and 8 (perfectly random).
Encrypted/compressed data: ~7.5-8.0
English text: ~4.0-5.0
Binary executables: ~5.0-6.5
"""
if not data:
return 0.0
# Count byte frequencies
freq = [0] * 256
for byte in data:
freq[byte] += 1
# Calculate entropy
length = len(data)
entropy = 0.0
for count in freq:
if count > 0:
p = count / length
entropy -= p * math.log2(p)
return entropy
def _get_pow_secret() -> bytes:
"""Get or generate the PoW signing secret."""
global _pow_secret_cache
@@ -425,6 +453,22 @@ def create_paste():
"authenticated": owner is not None,
}, 413)
# Check minimum entropy requirement (encryption enforcement)
min_entropy = current_app.config.get("MIN_ENTROPY", 0)
if min_entropy > 0:
entropy = _calculate_entropy(content)
if entropy < min_entropy:
current_app.logger.warning(
"Low entropy rejected: %.2f < %.2f from=%s",
entropy, min_entropy, request.remote_addr
)
return _json_response({
"error": "Content entropy too low",
"entropy": round(entropy, 2),
"min_entropy": min_entropy,
"hint": "Encrypt content before uploading (-e flag in fpaste)",
}, 400)
# Check content deduplication threshold
content_hash = hashlib.sha256(content).hexdigest()
is_allowed, dedup_count = check_content_hash(content_hash)

View File

@@ -27,6 +27,11 @@ class Config:
CONTENT_DEDUP_WINDOW = int(os.environ.get("FLASKPASTE_DEDUP_WINDOW", 3600)) # 1 hour
CONTENT_DEDUP_MAX = int(os.environ.get("FLASKPASTE_DEDUP_MAX", 3)) # max 3 per window
# 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
MIN_ENTROPY = float(os.environ.get("FLASKPASTE_MIN_ENTROPY", 0))
# Reverse proxy trust configuration
# SECURITY: The X-SSL-Client-SHA1 header is trusted for authentication.
# This header MUST only come from a trusted reverse proxy that validates