fix(cv): harden document parsing

Upgrade and hash-lock upload-facing parser dependencies, reject resource-heavy or mismatched inputs, remove unsafe backend binary fallbacks, and prevent internal parser failures from leaking to users.
This commit is contained in:
cesnimda
2026-08-30 11:00:52 +02:00
parent a74daa7aa4
commit a8bf505ce5
14 changed files with 1869 additions and 110 deletions
+83
View File
@@ -2,9 +2,12 @@ import importlib
import io
import json
import sys
import zipfile
from pathlib import Path
from fastapi.testclient import TestClient
from PIL import Image
from pypdf import PdfWriter
ROOT = Path(__file__).resolve().parents[1]
@@ -523,6 +526,86 @@ def test_extract_text_rejects_oversized_upload_before_parsing(monkeypatch):
assert "too large" in response.json()["detail"].lower()
def test_extract_text_rejects_extension_signature_mismatch(monkeypatch):
module = load_app_module(monkeypatch)
client = TestClient(module.app)
response = client.post("/extract-text", files={"file": ("resume.pdf", b"not a pdf", "application/pdf")})
assert response.status_code == 400
assert response.json()["detail"] == "The uploaded file does not match its PDF extension."
def test_extract_text_rejects_binary_plain_text(monkeypatch):
module = load_app_module(monkeypatch)
client = TestClient(module.app)
response = client.post("/extract-text", files={"file": ("resume.txt", b"Name\x00binary", "text/plain")})
assert response.status_code == 400
assert response.json()["detail"] == "The uploaded text file contains binary data."
def test_extract_text_rejects_pdf_over_page_limit(monkeypatch):
module = load_app_module(monkeypatch)
monkeypatch.setattr(module, "MAX_PDF_PAGES", 1)
writer = PdfWriter()
writer.add_blank_page(width=100, height=100)
writer.add_blank_page(width=100, height=100)
payload = io.BytesIO()
writer.write(payload)
client = TestClient(module.app)
response = client.post("/extract-text", files={"file": ("resume.pdf", payload.getvalue(), "application/pdf")})
assert response.status_code == 422
assert response.json()["detail"] == "The PDF contains too many pages."
def test_extract_text_rejects_image_pixel_limit_before_ocr(monkeypatch):
module = load_app_module(monkeypatch)
monkeypatch.setattr(module, "MAX_IMAGE_PIXELS", 50)
monkeypatch.setattr(module, "_ocr_image", lambda image: (_ for _ in ()).throw(AssertionError("OCR must not run")))
payload = io.BytesIO()
Image.new("RGB", (10, 10), "white").save(payload, format="PNG")
client = TestClient(module.app)
response = client.post("/extract-text", files={"file": ("resume.png", payload.getvalue(), "image/png")})
assert response.status_code == 422
assert response.json()["detail"] == "The document image contains too many pixels."
def test_docx_container_rejects_excessive_entries(monkeypatch):
module = load_app_module(monkeypatch)
monkeypatch.setattr(module, "MAX_DOCX_ENTRIES", 2)
payload = io.BytesIO()
with zipfile.ZipFile(payload, "w") as archive:
archive.writestr("[Content_Types].xml", "types")
archive.writestr("word/document.xml", "document")
archive.writestr("word/styles.xml", "styles")
try:
module._validate_docx_container(payload.getvalue())
except module.HTTPException as exc:
assert exc.status_code == 422
assert exc.detail == "The DOCX contains too many files."
else:
raise AssertionError("Expected the DOCX entry limit to reject the container")
def test_extract_text_does_not_leak_parser_exception(monkeypatch):
module = load_app_module(monkeypatch)
monkeypatch.setattr(module, "_extract_pdf_text", lambda data: (_ for _ in ()).throw(RuntimeError("private path C:/secret")))
client = TestClient(module.app)
response = client.post("/extract-text", files={"file": ("resume.pdf", b"%PDF-invalid", "application/pdf")})
assert response.status_code == 422
assert response.json()["detail"] == "Document extraction failed."
assert "secret" not in response.text
def test_extraction_normalization_preserves_sections_and_bullets(monkeypatch):
module = load_app_module(monkeypatch)