forked from username/flaskpaste
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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user