Docling EasyOCR model ZIP extraction boundary¶
Sources: GHSA-cjqg-rq2h-2fvj, Docling advisory, Docling 2.91.0 release
Affected package: PyPI docling < 2.91.0, patched in 2.91.0.
Operator value: source-assisted validation of model-download and archive-extraction boundaries in AI/OCR document-processing pipelines.
Why this matters¶
GHSA-cjqg-rq2h-2fvj documents unsafe ZIP extraction in Docling's EasyOCR model download path. Before 2.91.0, ZIP members were extracted without validating that each member stayed under the intended model directory. If an attacker controls, compromises, or can intercept the model archive source, traversal members such as ../ paths can write outside the model cache to locations writable by the Docling process.
The durable testing lesson is: model bootstrap code is supply-chain attack surface when it downloads and extracts archives at runtime. Treat OCR, ML, and document-conversion model downloads like plugin installers: verify archive member canonicalization, destination confinement, integrity checks, and write permissions before relying on the pipeline boundary.
Recon targets¶
Prioritize explicitly authorized environments where Docling or similar document-processing services download OCR/model assets dynamically:
- ingestion workers that call Docling on user-uploaded PDFs, images, Office files, or scanned forms;
- container images that bootstrap EasyOCR or other models on first run instead of baking them into the image;
- internal data-processing jobs with broad write access to application, cache, home, startup, or SSH directories;
- air-gapped or proxy-routed deployments that mirror model assets from internal object storage;
- CI, notebook, or agent pipelines that install
doclingand run document extraction against untrusted samples.
Source-review starting points:
# Dependency reachability.
grep -R 'docling\|easyocr' -n requirements*.txt pyproject.toml poetry.lock uv.lock Pipfile.lock setup.cfg setup.py 2>/dev/null
python - <<'PY'
import importlib.metadata as m
for pkg in ['docling', 'easyocr']:
try:
print(pkg, m.version(pkg))
except m.PackageNotFoundError:
pass
PY
# Runtime model/bootstrap paths.
grep -R 'download_model\|EasyOCR\|easyocr\|model.*zip\|zipfile\|extractall' -n . 2>/dev/null
A dependency-only match is not enough for a strong finding. Look for a reachable code path where the running service downloads and extracts model archives, especially when the download source is configurable or mirrored.
Safe validation workflow¶
Use a local lab, a disposable container, or an explicitly authorized internal environment. Do not attempt to poison public model hosts, tamper with shared mirrors, or intercept traffic outside the approved test scope.
1. Confirm version and reachability¶
python - <<'PY'
import importlib.metadata as m
for pkg in ['docling', 'easyocr']:
try:
print(f'{pkg}=={m.version(pkg)}')
except m.PackageNotFoundError:
print(f'{pkg}: not installed')
PY
Then identify whether the application actually triggers Docling's EasyOCR model-download functionality at runtime. Prefer source review, container entrypoint review, and lab traces over production probing.
2. Map the archive-to-filesystem boundary¶
Document the boundary before testing:
| Boundary | Question to answer |
|---|---|
| Download source | Is the model archive fetched from a public URL, internal mirror, object bucket, proxy, or configurable endpoint? |
| Transport/integrity | Are TLS validation, checksums, signatures, or pinned digests enforced before extraction? |
| Extraction root | Which cache/model directory receives the archive contents? |
| Canonicalization | Are ZIP member paths resolved with realpath or equivalent before writes? |
| Privilege | Which user owns the process, and what paths can it write? |
| Trigger | Can an attacker influence when model download/bootstrap happens? |
3. Use an inert ZIP Slip canary in a lab¶
Create only harmless marker files in a disposable directory. Keep canaries outside secrets, startup paths, shell configuration, or executable search paths.
python - <<'PY'
from pathlib import Path
from zipfile import ZipFile
lab = Path('/tmp/docling-zip-slip-lab')
lab.mkdir(parents=True, exist_ok=True)
archive = lab / 'model-canary.zip'
with ZipFile(archive, 'w') as z:
z.writestr('safe/model.bin', 'safe marker\n')
z.writestr('../zip-slip-canary.txt', 'inert traversal marker\n')
print(archive)
PY
Validation evidence should show the behavioral difference between affected and patched extraction logic:
- affected logic writes or attempts to write a traversal member outside the extraction root in the lab;
- patched logic rejects the traversal member or confines extraction under the intended directory;
- the running application path uses
docling< 2.91.0and can reach the model-download extraction branch.
Do not replace real model archives or point production services at a malicious archive unless the program owner explicitly approves that supply-chain simulation.
4. Controls to avoid false positives¶
| Control | Expected result |
|---|---|
docling 2.91.0 or later | Traversal members are rejected or remain confined to the extraction root |
| Safe ZIP with normal relative members | Model extraction succeeds inside the intended directory |
| Traversal ZIP against a generic scanner only | Do not report unless Docling's reachable model-download path is affected |
| Non-writable parent directories | Impact is limited to paths writable by the process user |
| Integrity-pinned internal model mirror | Poisoning the archive source requires a separate mirror/integrity bypass |
If the application has a separate user-controlled archive upload/extraction path, report that as its own archive traversal issue rather than overfitting it to GHSA-cjqg-rq2h-2fvj.
Reporting heuristic¶
Frame the finding around a model-bootstrap archive boundary:
- Expected boundary: model ZIP members must be canonicalized and constrained under the model cache before any write.
- Observed bypass: a Docling
< 2.91.0EasyOCR model-download path extracts traversal members outside the target directory in a lab or authorized mirror simulation. - Impact: arbitrary file write as the Docling process user if the model archive source is attacker-controlled, compromised, or intercepted.
- Evidence: package version, reachable model-download trigger, archive source path, redacted configuration, inert canary result, and patched-version control.
Scope and safety notes¶
- Keep testing to lab or explicitly approved mirror-simulation environments.
- Do not tamper with public model sources or shared production mirrors.
- Avoid writing to startup scripts, SSH keys, package files, or executable paths unless explicit command-execution validation is authorized.
- Do not collect documents, OCR outputs, credentials, or model artifacts unrelated to the boundary proof.