Skip to content

fix(security): backport DoS caps + log hygiene lost in the refactor - #591

Merged
andyne13 merged 5 commits into
refactor/hexagonalfrom
fix/v2-security-backport
Jun 30, 2026
Merged

fix(security): backport DoS caps + log hygiene lost in the refactor#591
andyne13 merged 5 commits into
refactor/hexagonalfrom
fix/v2-security-backport

Conversation

@andyne13

@andyne13 andyne13 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What

Backports the v1.1.x security-hardening fixes that the hexagonal refactor dropped (relocated code reimplemented without the protections). All are confirmed reachable in hexagonal.

1. Bound upload size (8ca8561e)

save_file_to_disk streamed uploads with no byte cap — the per-user quota limits file count, not bytes, so one request could write an arbitrarily large file and exhaust disk/RAM. Now enforces MAX_UPLOAD_SIZE_MB (default 1024) during streaming, returns 413, and removes the partial file on overflow.

2. Parser fan-out/page caps + DOCX memory-bomb fix (221f8ed8)

The refactor reintroduced an attacker-controlled allocation: DocxParser._extract_embedded_images built [None] * max_order, where max_order is parsed from the untrusted word/media/imageN filename — a small crafted DOCX (e.g. image999999999.png) could OOM the indexer.

  • DOCX: cap media entries + per-entry decompressed size; skip non-positive indices; only materialise the positional array within the cap, else compact ordered list.
  • PPTX: cap slides walked and pictures decoded into memory.
  • PDF (marker): cap pages processed per file.

EML fan-out/recursion caps were already carried over (_MAX_EML_ATTACHMENTS + _build_eml depth bound) and are unchanged. New caps follow the EML parser's existing module-constant pattern.

3. Log hygiene (1bfca310)

DEBUG was the default level and the web-search zero-results warning logged raw query text (survives an INFO default, persists to the long-lived JSON sink).

  • conf/config.yaml + infra/compose/.env.example: default log level INFO.
  • websearch/service.py: drop the query string from the zero-results warning.

Investigated and intentionally NOT included

  • Image-URL SSRF guard (63a857af) — the pre-refactor sink was loaders/base.py:get_image_description, which forwarded a raw document image URL to the VLM. Hexagonal removed that sink: the markdown parser stores source_url but never fetches it, and the caption stage passes image_bytes (not the URL) to the VLM. No reachable SSRF exists today. Tracked as defense-in-depth for if a remote-image fetch stage is ever wired (restore image_captioning_url=false + SSRF guard at that point).

Tests

  • test_save_file_to_disk_rejects_oversize_upload (413 + partial cleanup)
  • test_huge_positional_index_does_not_allocate (memory-bomb regression), test_non_positive_index_skipped
  • Full unit suite green (1520 passed); ruff check/format + layer-import guard pass.

Context

Pre-v2.0 backport audit of v1.1.12–1.1.13 fixes vs refactor/hexagonal. The remaining audit items were all confirmed already present; this PR closes the real gaps.

Summary by CodeRabbit

  • New Features
    • Added safety caps for DOCX, PPTX, and PDF ingestion to reduce resource exhaustion risk.
    • Enforced environment-driven upload-size limits; oversized uploads are rejected and any partially saved file is removed.
  • Bug Fixes
    • Admin indexing and tool execution now preserve domain-level error HTTP responses instead of being wrapped as generic server errors.
    • Search “zero results” warnings no longer include the original query text.
  • Documentation
    • Updated default verbose logging level to INFO and clarified upload-size configuration behavior.
  • Tests
    • Added coverage for oversize upload handling and ingestion cap behavior.

andyne13 added 2 commits June 29, 2026 22:42
Backport of 8ca8561 (v1.1.x hardening, M8 family) lost in the hexagonal
refactor. save_file_to_disk streamed uploads with no byte cap; the per-user
quota limits file count, not bytes, so one request could write an arbitrarily
large file and exhaust disk/RAM. Enforce a configurable max (MAX_UPLOAD_SIZE_MB,
default 1024) during streaming, returning 413 and removing the partial file
when exceeded.
Backport of 221f8ed (M8). The hexagonal refactor dropped the parser-bomb caps
and reintroduced the attacker-controlled allocation the original fix removed:
DocxParser._extract_embedded_images built [None]*max_order where max_order is
parsed from the untrusted word/media/imageN filename -> a small crafted DOCX
could OOM the indexer.

- DOCX: cap embedded media entries iterated and per-entry decompressed size;
  skip non-positive indices; only materialise the positional array when the max
  index is within the cap, else fall back to a compact ordered list.
- PPTX: cap slides walked and pictures decoded into memory.
- PDF (marker): cap pages processed per file (_MAX_PDF_PAGES).

EML fan-out/recursion caps were already carried over (_MAX_EML_ATTACHMENTS +
_build_eml depth bound), so are unchanged. Caps follow the existing module-level
constant pattern used by the EML parser.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 58f78762-74de-4e49-b858-1f9bf18b2b8c

📥 Commits

Reviewing files that changed from the base of the PR and between 697aabe and 48be90e.

📒 Files selected for processing (3)
  • infra/compose/.env.example
  • openrag/api/dependencies/files.py
  • tests/unit/api/dependencies/test_files.py
✅ Files skipped from review due to trivial changes (1)
  • infra/compose/.env.example
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/api/dependencies/test_files.py

📝 Walkthrough

Walkthrough

Adds upload-size enforcement, parser safety caps for PDF/PPTX/DOCX ingestion, and logging default changes that lower verbosity and omit query text from a web search warning.

Changes

Parser-bomb, upload, and logging caps

Layer / File(s) Summary
Upload size enforcement and error propagation
infra/compose/.env.example, openrag/api/dependencies/files.py, openrag/api/routers/admin/indexing.py, openrag/api/routers/admin/tools.py, tests/unit/api/dependencies/test_files.py, tests/unit/api/routers/admin/test_indexing_upload_errors.py
Reads MAX_UPLOAD_SIZE_MB at call time, enforces streamed upload limits with HTTP 413, removes partial files on overflow, preserves domain errors in the admin upload and tool routes, and adds regression coverage for the rejection path. The env template documents the variable and the tests cover rejection, cleanup, and call-time env reads.
DOCX embedded-media extraction caps
openrag/core/indexing/parsers/docx_parser.py, tests/unit/core/indexing/parsers/test_docx_parser.py
Adds caps for embedded-media entry count and per-entry decompressed size, skips invalid or oversized media entries, and avoids large positional allocations when rebuilding extracted image order. Tests cover huge and non-positive filename indexes.
PPTX slide and image caps
openrag/core/indexing/parsers/pptx_parser.py
Adds slide and image limits to PPTX conversion and stops iterating or decoding once the configured caps are reached, while keeping the returned slide count based on the full presentation.
PDF page cap in MarkerPool.process_pdf
openrag/services/workers/parsers/marker_workers.py
Adds _MAX_PDF_PAGES and applies it before chunking so both the chunk_size <= 0 path and the single-chunk path use an explicit page_range when capped.
Logging defaults and redaction
conf/config.yaml, infra/compose/.env.example, openrag/services/websearch/service.py
Switches default verbose and example log levels from DEBUG to INFO, adds comments about when DEBUG is appropriate, and changes the zero-results web search warning to omit the query text.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through caps and limits new,
Kept big old uploads from slipping through.
The logs grew quiet, the parsers stayed neat,
And INFO now hums a softer beat.
Hop hop, the burrow is snug and bright ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main security hardening and log-hygiene changes backported in this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/v2-security-backport

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce5842c57a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openrag/api/dependencies/files.py
Comment thread openrag/api/dependencies/files.py Outdated
Comment thread openrag/api/dependencies/files.py Outdated
Comment thread openrag/core/indexing/parsers/docx_parser.py
…ch log

Backport of 1bfca31 (M10). DEBUG was the default level and the web-search
zero-results warning logged the raw query text (survives an INFO default and
persists to the long-lived JSON sink), leaking potentially sensitive request
content.

- conf/config.yaml + infra/compose/.env.example: default log level INFO.
- websearch/service.py: drop the query string from the zero-results warning.

The pre-refactor pipeline.py temporal-filter warning that also logged the query
has no hexagonal equivalent (that retry path no longer logs the query), so needs
no change. Search endpoints already log query_len, not the text.
@coderabbitai coderabbitai Bot added the fix Fix issue label Jun 29, 2026
@andyne13 andyne13 changed the title fix(security): backport upload-size + parser-bomb DoS caps lost in the refactor fix(security): backport DoS caps + log hygiene lost in the refactor Jun 29, 2026
MAX_UPLOAD_SIZE_BYTES was evaluated at import, but api.main imports the
admin routers (and thus this module) before it calls load_config()/
load_dotenv(). A MAX_UPLOAD_SIZE_MB set in .env was therefore ignored on
local starts and the process silently used the 1024 MB default. Read the
value lazily inside save_file_to_disk via _max_upload_size_bytes().
save_file_to_disk raises ValidationError (413 for oversize uploads, 400 for
bad filenames), but add_file and execute_tool wrapped it in a broad
'except Exception' that re-raised HTTP 500 — so the new payload-too-large
contract looked like a server failure. Let OpenRAGError propagate to the
registered handler, which maps it to its declared status. Adds a route-level
regression test asserting 413 (not 500) for an oversize upload.
@coderabbitai coderabbitai Bot removed the fix Fix issue label Jun 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openrag/api/dependencies/files.py`:
- Around line 16-28: `_max_upload_size_bytes` currently treats
`MAX_UPLOAD_SIZE_MB=0` as unlimited, but zero should be an enforced zero-byte
cap and only negative values should disable the limit. Update the docstring in
`_max_upload_size_bytes` to reflect that negative values disable enforcement,
then change the upload-size check in the file upload path (the `max_bytes` / `>
0` guard) so enforcement runs for `0` as well. Finally, adjust the env-based
test to expect non-empty uploads to be rejected when `MAX_UPLOAD_SIZE_MB` is
`0`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b20bd25c-789e-4a95-9200-719ea34b6f43

📥 Commits

Reviewing files that changed from the base of the PR and between eca396d and 697aabe.

📒 Files selected for processing (5)
  • openrag/api/dependencies/files.py
  • openrag/api/routers/admin/indexing.py
  • openrag/api/routers/admin/tools.py
  • tests/unit/api/dependencies/test_files.py
  • tests/unit/api/routers/admin/test_indexing_upload_errors.py

Comment thread openrag/api/dependencies/files.py
@Ahmath-Gadji
Ahmath-Gadji force-pushed the fix/v2-security-backport branch from 48be90e to 697aabe Compare June 30, 2026 08:19

@Ahmath-Gadji Ahmath-Gadji left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Fix issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants