forked from username/flaskpaste
add Hypothesis property-based MIME detection tests
- test_magic_prefix_detection: verify all signatures with random suffix - test_random_binary_never_crashes: random data never crashes - test_partial_magic_no_false_match: truncated magic handled safely - test_magic_not_at_start_ignored: only detect magic at offset 0
This commit is contained in:
@@ -168,6 +168,108 @@ class TestMimeTypeFuzzing:
|
||||
pass
|
||||
|
||||
|
||||
class TestMimeDetectionFuzzing:
|
||||
"""Property-based tests for MIME magic byte detection."""
|
||||
|
||||
# Known magic signatures mapped to expected MIME types
|
||||
MAGIC_SIGNATURES: ClassVar[list[tuple[bytes, str]]] = [
|
||||
(b"\x89PNG\r\n\x1a\n", "image/png"),
|
||||
(b"\xff\xd8\xff", "image/jpeg"),
|
||||
(b"GIF87a", "image/gif"),
|
||||
(b"GIF89a", "image/gif"),
|
||||
(b"%PDF", "application/pdf"),
|
||||
(b"PK\x03\x04", "application/zip"),
|
||||
(b"\x1f\x8b", "application/gzip"),
|
||||
(b"fLaC", "audio/flac"),
|
||||
(b"OggS", "audio/ogg"),
|
||||
(b"ID3", "audio/mpeg"),
|
||||
(b"\x7fELF", "application/x-executable"),
|
||||
(b"MZ", "application/x-msdownload"),
|
||||
(b"BZh", "application/x-bzip2"),
|
||||
(b"7z\xbc\xaf\x27\x1c", "application/x-7z-compressed"),
|
||||
(b"SQLite format 3\x00", "application/x-sqlite3"),
|
||||
# HEIC/HEIF/AVIF (ftyp box format)
|
||||
(b"\x00\x00\x00\x18\x66\x74\x79\x70\x68\x65\x69\x63", "image/heic"),
|
||||
(b"\x00\x00\x00\x18\x66\x74\x79\x70\x6d\x69\x66\x31", "image/heif"),
|
||||
(b"\x00\x00\x00\x1c\x66\x74\x79\x70\x61\x76\x69\x66", "image/avif"),
|
||||
]
|
||||
|
||||
@settings(max_examples=100, suppress_health_check=FIXTURE_HEALTH_CHECKS)
|
||||
@given(suffix=st.binary(min_size=0, max_size=1000))
|
||||
def test_magic_prefix_detection(self, client, suffix):
|
||||
"""Magic bytes followed by arbitrary data should detect correctly."""
|
||||
for magic, expected_mime in self.MAGIC_SIGNATURES:
|
||||
content = magic + suffix
|
||||
response = client.post(
|
||||
"/",
|
||||
data=content,
|
||||
content_type="application/octet-stream",
|
||||
)
|
||||
if response.status_code == 201:
|
||||
data = json.loads(response.data)
|
||||
assert data["mime_type"] == expected_mime, (
|
||||
f"Expected {expected_mime} for magic {magic!r}, got {data['mime_type']}"
|
||||
)
|
||||
|
||||
@settings(max_examples=200, suppress_health_check=FIXTURE_HEALTH_CHECKS)
|
||||
@given(content=st.binary(min_size=1, max_size=5000))
|
||||
def test_random_binary_never_crashes(self, client, content):
|
||||
"""Random binary content should never crash MIME detection."""
|
||||
response = client.post(
|
||||
"/",
|
||||
data=content,
|
||||
content_type="application/octet-stream",
|
||||
)
|
||||
assert response.status_code in (201, 400, 413, 429, 503)
|
||||
if response.status_code == 201:
|
||||
data = json.loads(response.data)
|
||||
# MIME type should always be a valid format
|
||||
assert "/" in data["mime_type"]
|
||||
assert len(data["mime_type"]) < 100
|
||||
|
||||
@settings(max_examples=100, suppress_health_check=FIXTURE_HEALTH_CHECKS)
|
||||
@given(
|
||||
magic=st.sampled_from([m for m, _ in MAGIC_SIGNATURES]),
|
||||
truncate=st.integers(min_value=1, max_value=10),
|
||||
)
|
||||
def test_partial_magic_no_false_match(self, client, magic, truncate):
|
||||
"""Truncated magic bytes should not produce false positive matches."""
|
||||
if truncate >= len(magic):
|
||||
return # Skip if we'd use full magic
|
||||
partial = magic[:truncate]
|
||||
# Add random suffix that's clearly not the rest of the magic
|
||||
content = partial + b"\xff\xfe\xfd\xfc" * 10
|
||||
|
||||
response = client.post(
|
||||
"/",
|
||||
data=content,
|
||||
content_type="application/octet-stream",
|
||||
)
|
||||
# Partial magic should not crash - may match different signature or fallback
|
||||
assert response.status_code in (201, 400, 413, 429, 503)
|
||||
|
||||
@settings(max_examples=50, suppress_health_check=FIXTURE_HEALTH_CHECKS)
|
||||
@given(
|
||||
content=st.binary(min_size=100, max_size=1000),
|
||||
inject_pos=st.integers(min_value=20, max_value=80),
|
||||
)
|
||||
def test_magic_not_at_start_ignored(self, client, content, inject_pos):
|
||||
"""Magic bytes not at offset 0 should not trigger detection."""
|
||||
# Inject PNG magic in middle of random data
|
||||
png_magic = b"\x89PNG\r\n\x1a\n"
|
||||
if inject_pos < len(content):
|
||||
modified = content[:inject_pos] + png_magic + content[inject_pos:]
|
||||
response = client.post(
|
||||
"/",
|
||||
data=modified,
|
||||
content_type="application/octet-stream",
|
||||
)
|
||||
if response.status_code == 201:
|
||||
data = json.loads(response.data)
|
||||
# Should NOT detect as PNG (magic not at start)
|
||||
assert data["mime_type"] != "image/png"
|
||||
|
||||
|
||||
class TestJsonFuzzing:
|
||||
"""Fuzz JSON endpoint handling."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user