Skip to content

fix: sanitize PDF export filenames against path traversal - #718

Open
tsumon wants to merge 1 commit into
666ghj:mainfrom
tsumon:fix/pdf-export-filename-sanitization
Open

tsumon wants to merge 1 commit into
666ghj:mainfrom
tsumon:fix/pdf-export-filename-sanitization

Conversation

@tsumon

@tsumon tsumon commented Sep 15, 2026

Copy link
Copy Markdown

Summary

Markdown export already sanitized the IR topic before writing a file. PDF export did not.

CLI export_pdf.py interpolated metadata.topic into final_reports/pdf/report_{topic}_{timestamp}.pdf. A topic such as ../../tmp/pwned writes outside the export directory.

The HTTP routes /api/report/export/pdf/<task_id> and /export/pdf-from-ir put the same raw topic into Content-Disposition: attachment; filename="...". CRLF or quotes in the topic become response-header injection.

This PR:

  • Adds ReportEngine/utils/filenames.py with a shared sanitizer (letters, digits, space, -, _; spaces become _).
  • Uses it for Markdown export, both PDF HTTP routes, and the CLI exporter.
  • Resolves the CLI output path and refuses to write outside final_reports/pdf.
  • Treats missing / non-dict metadata as a fallback (task.query or "report") instead of crashing on .get.

No Pango/WeasyPrint required for the tests; PDF rendering is stubbed.

How to verify

python -m pytest tests/test_filenames.py tests/test_export_pdf_paths.py tests/test_export_pdf_headers.py tests/test_report_cancel_route.py -q

18 passed.

Markdown export already used a safe filename fragment. PDF export
interpolated the IR topic into the filesystem path and the
Content-Disposition header, so a topic containing ../ or CRLF could
write outside final_reports/pdf or inject response headers.

Share one sanitizer for MD/PDF/CLI, keep the CLI output path inside
the export directory, and add regression tests.
@666ghj 666ghj added the valuable feedback Good for newcomers label Oct 1, 2026
@666ghj

666ghj commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Agent automated reply on behalf of the maintainer.

Thank you for this focused export improvement. Sharing filename handling between the export paths and checking the CLI output directory are useful changes.

Our read-only review found a fallback issue that needs correction before merge. sanitize_filename_segment() returns fallback unchanged when the primary value becomes empty, and the HTTP routes pass the original task.query as that fallback. For example, a topic of /// and a query of A/B comparison put a slash back into the final filename. This also regresses the existing Markdown helper, which fell back to the fixed value report.

Please sanitize the fallback too, then use a fixed safe default if both values are empty. Add regression cases for an empty sanitized topic combined with a query containing slashes, quotes, or line breaks, including a Markdown export route test.

Please also distinguish filename and response-header validation from proven header injection in the description: Werkzeug rejects header values containing CR or LF. The renderer stubs are appropriate for focused filename tests, but do not establish real PDF rendering compatibility.

We have not run this PR's tests in this pass. We will keep the PR open for the focused fixes and subsequent local validation.

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

Labels

valuable feedback Good for newcomers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants