跳转至

安全

只有PyPI上的最新版本会获得安全修复。

报告漏洞

请通过GitHub Security Advisories 私密报告漏洞,不要创建公开Issue。我们会争取在一周内首次响应。

处理不受信任的PDF

pylopdf由Rust编写且没有必需的Python依赖,但解析恶意PDF输入仍然存在固有风险。

使用完整的资源策略

请向pylopdf.open()传入limits=pylopdf.DocumentLimits.web()。 该预设适合作为内存受限Web或queue worker处理用户上传内容的保守起点。

import pylopdf

try:
    with pylopdf.open(
        "upload.pdf",
        limits=pylopdf.DocumentLimits.web(),
    ) as doc:
        facts = doc.complexity
        preview = doc[0].get_pixmap(dpi=144)
except pylopdf.LimitError as error:
    reject_upload(error.code)

Web预设目前独立应用以下上限:

资源 上限
输入文件 10 MiB
页数 200
间接对象 100,000
单个解码流(包括图像RGBA估算) 64 MiB
单个页面内容流 10 MiB
所有流累计解码或估算字节 128 MiB
直接array/dictionary嵌套深度 64
已解释页面累计UTF-8 glyph payload 1 MiB
传给rendering/extraction的完整PDF snapshot 64 MiB
已解释页面累计带位置glyph record 65,536

如需适配其他负载,可直接构造DocumentLimits(...)。每个非None值必须是正整数。 原有max_decompressed_size=仍可作为单流预算的兼容简写,但不能与limits=同时使用。

所有公开page indexing都使用同一个迭代page-tree walker,包括page count/lookup、 complexity、render snapshot、TOC/link、annotation、Form import与页面结构编辑。 它会拒绝重复使用的Page/Pages对象与cycle、超过32次引用的间接/Kids chain、 超过256层的内部tree,以及超过间接对象数的edge。max_pages遇到第一个超限页面时 停止。未配置页面策略的open保持兼容,并将这些page-tree检查延迟到第一次 page-indexing操作。

LimitErrorPdfError的子类。稳定的codefile_sizepage_countobject_countobject_depthdecompressed_sizepage_content_sizetotal_decompressed_sizetext_sizetext_glyph_countinterpretation_sizeembedded_file_sizeembedded_file_input_sizeform_field_input_sizeform_content_sizeannotation_input_sizemetadata_input_sizetoc_input_sizepage_label_input_sizexmp_metadata_sizerender_output_sizemarkdown_output_sizesvg_output_sizereplacement_input_sizereplacement_output_sizepdf_output_sizeimage_input_sizeimage_pixel_countfont_input_sizetext_input_sizetext_line_countsearch_input_sizesearch_hit_countpassword_input_sizepixmap_output_sizeocr_model_sizeocr_dictionary_entriesstream_filter_countdecompression_unverifiable之一; 同一值也位于error.args[0]。无法安全估算上限的filter chain会被拒绝,而不是 乐观解码。pylopdf自有的有界lopdf解码路径还会在materialize filter列表前拒绝 超过16层的/Filter chain。

doc.complexity无需解码流或调用renderer,即可报告页数、对象数、流数、编码流 字节数以及直接对象最大深度,适合在重型提取前进行routing。结构和解压预算验证 打开时的source;生成结果若要跨越新的trust boundary,请用同一策略重新打开。 直接深度验证与complexity检查的迭代遍历stack采用fallible扩展,因此allocation refusal会抛出PdfError,而不会暴露部分facts。

max_interpretation_size在hayro首次读取保留input,以及pylopdf在编辑、解密或 AcroForm state选择后serialize当前状态时生效。有界writer会拒绝越界write,且不会 安装不完整的renderer/extractor cache。为保持兼容,默认值为NoneDocumentLimits.web()使用64 MiB。

设置max_text_size后,plain-text提取会预检组装后精确的UTF-8大小,并将一个private batch限制为glyph payload预算的两倍。每个非空glyph至少贡献一个payload字节,而推断 gap与换行的总数不会超过glyph数。batch最多接受4,096个page entry,因此重复页码无法 绕过策略。拒绝code仍为text_size;为保持兼容,默认值仍为None

max_text_glyphs限制line组装前保留的record数,因此也限制结构化文本可materialize的 block、line、span和word数量。同一页的文本与表格解释共享一次累计admission,被拒绝 的页面不消耗预算。为保持兼容,默认值为NoneDocumentLimits.web()使用65,536。

宽松打开只执行一种受限修复:仅当同一最终revision中存在完整classic xref table, 并且在原有上限下完整解析成功时,才替换错误的最终startxref。它不会扫描object header、修复xref stream或回退到旧revision。修复会发出PylopdfWarning,令 doc.is_repaired(metadata probe中的repaired)为True;保存会规范化xref数据。

  • 每页渲染上限为6400万像素。
  • Document.render_page()Page.render()Pixmap.tobytes()的encoded PNG 输出默认上限为64 MiB。writer会在返回Python bytes前拒绝越界write; max_size=None可显式取消。rendering使用render_output_size, Pixmap直接encode使用pixmap_output_size
  • Document.tobytes()对普通、object/xref stream及加密输出统一应用512 MiB默认 serialization上限。Rust writer会在转换为Python bytes之前拒绝越界write; max_size=None可显式取消。save()先流式写入target同directory中安全创建的 sibling,仅在完整写入后原子替换请求path,因此serialization或替换失败会保留 现有file。它不受此in-memory预算限制。即使后续I/O失败,garbagedeflateobject_streams等save option仍保持已记录的mutation语义。
  • Pixmap.save()把PNG encode直接流式写入target directory中不可预测且排他创建的 sibling,仅在完整write成功后原子替换请求path,不会在内存中再保留一份完成PNG。 替换失败会保留现有output并删除临时file。
  • Page.insert_image()默认将encoded JPEG/PNG input限制为64 MiB,将decoded PNG input限制为64,000,000像素。filename通过释放GIL的Rust边界进行有上限读取,PNG dimension在分配decoded storage前检查。可信workload可用max_size=Nonemax_pixels=None显式取消。
  • insert_textinsert_textboxset_form_fieldset_fallback_font的 显式/自动OpenType input默认限制为64 MiB。buffer在PyO3 copy前拒绝,filename 通过释放GIL的有界Rust path读取。可信workload可用max_font_size=None显式取消。
  • insert_text()insert_textbox()的生成文本输入默认限制为1 MiB UTF-8, 物理行与换行后layout限制为4,096行。Python在PyO3 copy前检查物理行,Rust边界 再次检查并在mutation前停止换行layout;textbox会在分配展开后的string前预检tab 展开量。可信插入input可用max_text_size=None显式取消,拒绝code为 text_input_sizetext_line_count。AcroForm文本/选项外观保留固定4,096行上限。 UAX #14/grapheme index、行collection与保留的行text均采用fallible扩展,因此 allocation refusal不会暴露部分layout或document编辑。pylopdf自有的规范化、 grapheme引用、shaped line/glyph collection、WinAnsi输出与输入派生的text operator 扩展也采用fallible处理。Standard 14宽度检查不会分配encoded text copy。 allocation refusal返回PdfError;插入保持不修改文档,form fill则原子rollback。
  • search_for()将搜索词限制为4,096 UTF-8 byte,返回geometry默认限制为4,096项。 Python在PyO3 copy前拒绝超限搜索词,Rust边界再次检查两项限制。可信结果集可用 max_hits=None显式取消;拒绝code为search_input_sizesearch_hit_count,且不返回partial list。
  • open、authenticate、快速metadata probe与AES-256输出使用的password在PyO3 copy或 password KDF前限制为127 UTF-8 byte。Rust直接调用会再次检查;拒绝code为 password_input_size,保存拒绝发生在document mutation或创建output之前。
  • render_page_svg()Page.render_svg()的UTF-8输出默认上限为64 MiB,在 PyO3创建Python string前拒绝超限结果;max_size=None可显式取消。 hayro-svg 0.7只返回完整String,因此pylopdf应用边界前的一份内部Rust string 不受此限制。
  • 绘图插入会在cache失效、输入decode或创建dependent object前检查page /Contents的raw array和引用chain。raw array上限为4,096个entry,chain深度为 32,最终array上限为4,096个stream引用(包括只添加一次的q/Q isolation pair)。失败时不修改document。
  • Page.replace_text()将search、replacement和fallback的合计限制为4,096个 UTF-8 byte,并为解码page content、font encoding data、替换增长和最终stream 设置64 MiB默认上限。它在commit前准备page专用stream,因此不会修改复制page的 共享content;no-match/error会保留document和cache。caller text会在PyO3 copy前 逐步计数,而不创建完整encoded copy。可信输入可用 max_size=None显式解除。
  • delete_pages()select()insert_pdf()在Python与Rust中每次call最多接受 4,096个page entry。iterable会在第4,097个item、graph修改前停止。空delete保留 cache、generation及现有Page view。
  • Page.get_images()会拒绝每页超过4,096个placement、累计64,000,000个source像素或 64 MiB返回payload的部分结果。Flate-wrapped JPEG直通也只解压到剩余byte预算。
  • Document.embfile_add()会在PyO3 copy前拒绝超过64 MiB的输入, embfile_get()对每个解码filter层采用相同默认上限。对于已知的大型附件可提高 max_size=max_size=None会显式接受无限制输入或materialization。附件name tree 超过4,096个entry/node、32层或encoded/decoded名称合计1 MiB时也会拒绝。 caller查找/删除名称与添加时key/filename/description输入会在tree遍历或data copy前 以1 MiB停止,并使用embedded_file_input_size。编辑会在clone inline FileSpec之前检查4,096个direct object、32层和1 MiB direct string/name/stream data上限,并预检Catalog写入目标,无需为rollback clone整个文档。
  • Document.get_pdfa_claim()默认将每个filter层的XMP解码输出限制为1 MiB。 对于已知的大型packet可提高max_size=max_size=None会显式接受无限制 materialization。
  • Page.insert_ocr_text_layer()在超过4,096个非空word或UTF-8文本合计1 MiB时 停止iterable materialization。core直接调用执行相同上限,在第65,535种CID分配前 停止,并在PDF变更前准备所有输入派生buffer。
  • 页码标签number tree会拒绝超过4,096个entry/node、32层或encoded/decoded style与prefix文本合计1 MiB的部分结果。引用cycle只访问一次,写入也执行相同的 entry/text上限,并在PyO3 copy前使用page_label_input_size
  • AcroForm field tree会拒绝超过4,096个entry/node、8,192条edge、64层、1 MiB encoded/decoded/returned名称或值、或4,096个choice value item的部分结果。 引用cycle只访问一次,继承值按每个返回leaf计入预算;填写也原子地执行相同的 tree上限与1 MiB caller名称/值上限。caller输入会在font发现、button lookup或 file读取前以form_field_input_size拒绝。
  • AcroForm button field会拒绝超过4,096个widget、8,192个normal appearance state entry、4,096个唯一返回state name或1 MiB encoded/returned state-name文本。 填写会在修改前计入缺少的Off/on state key。
  • 批注与link读取会拒绝超过4,096个/Annots entry或每次调用aggregate encoded/returned metadata文本1 MiB的部分结果。添加会在创建dependent object和 失效cache之前检查相同的页面数量、生成subtype加Contents/URI输入合计1 MiB与 4,096个highlight矩形。caller text会在PyO3 copy或rectangle iteration前以 annotation_input_size拒绝,highlight iteration在第4,097个item停止。
  • named destination lookup只访问引用cycle一次,并拒绝超过4,096个entry/node、 8,192条edge、32层或1 MiB key byte的tree,而不会静默地将截断结果报告为未解析。 Page.get_links()每次call只构建一个borrowed index,不会为每个named link重复遍历。
  • TOC读取使用迭代式outline walk,只访问引用cycle一次并释放GIL;超过4,096个 node/entry、8,192条edge、64层、32层destination间接引用或1 MiB source/returned 文本时拒绝部分结果。写入会在PyO3 copy与修改前以toc_input_size检查entry、 深度和title文本上限。
  • Document.metadata只解码8个标准Info字段;aggregate source/returned文本超过 1 MiB时会拒绝,custom entry不会materialize为Python输出。 peek_metadata(max_file_size=)可在解析前拒绝path或byte input,并限制returned 标准文本;输入默认不设上限。写入在PyO3 copy前以metadata_input_size检查 1 MiB source/encoded文本并原子应用。
  • 嵌入JavaScript在设计上不受支持,也绝不会执行。
  • render_pages()最多接受4,096个page entry,累计encoded PNG默认上限为512 MiB。 并行结果共享一个atomic budget,失败时不返回部分list;max_size=None可显式 取消。worker admission另行限制live raster/conversion buffer;不要在application 层叠加无限并行。
  • Document.to_markdown()最多接受4,096个page entry,累计UTF-8输出默认上限为 64 MiB。heading size统计pass与render pass都只同时保留一页的interpreted layout、table和word。在组装page输出前,每个table都会获得剩余的累计预算。 Table.to_markdown()默认使用同一上限,并预检包含合并单元格展开在内的转义后 精确UTF-8大小。标题、段落、列表和表格在保留entry时计入预算,完整size获准后 再线性组装page。超过上限时不返回部分string;max_size=None可显式取消。
  • CPU deadline应由Worker、process或container宿主执行。资源预算限制已记录的 allocation和输出增长,但不会按wall-clock时间中断正在运行的parser或interpreter。
  • 批量处理不受信任的文件时,尽量在sandbox或container中运行。 native与Pyodide CI共享同一hostile-input回归契约;定期Atheris fuzzing使用 损坏xref、循环、深层对象、broken stream和压缩bomb作为seed。

依赖审计

CI会在每次push时运行cargo audit,使用RustSec漏洞数据库审计Rust依赖树。

本政策在仓库中的正本为 SECURITY.md