Skip to content

🛡️ Sentinel: [CRITICAL] Fix missing -no-shell-escape in PDF generation#321

Open
anchapin wants to merge 1 commit into
mainfrom
sentinel-fix-pdf-no-shell-escape-7938752640156516623
Open

🛡️ Sentinel: [CRITICAL] Fix missing -no-shell-escape in PDF generation#321
anchapin wants to merge 1 commit into
mainfrom
sentinel-fix-pdf-no-shell-escape-7938752640156516623

Conversation

@anchapin
Copy link
Copy Markdown
Owner

@anchapin anchapin commented May 26, 2026

🚨 Severity: CRITICAL
💡 Vulnerability: Missing -no-shell-escape flags in pdflatex and pandoc calls allowed Remote Code Execution (RCE) via \write18 if malicious LaTeX was processed. Missing timeouts allowed Denial of Service (DoS) via infinite compilation loops.
🎯 Impact: An attacker could execute arbitrary commands on the system or exhaust server resources.
🔧 Fix: Added -no-shell-escape / --pdf-engine-opt=-no-shell-escape flags and 30-second timeouts with proper cleanup to all PDF compilation subprocess calls in cli/pdf/converter.py and cli/generators/cover_letter_generator.py.
✅ Verification: Ran python -m pytest tests/test_pdf_security.py and full test suite to ensure tests pass and subprocess timeouts/flags are correctly handled.


PR created automatically by Jules for task 7938752640156516623 started by @anchapin

Summary by Sourcery

Harden PDF generation by enforcing safe LaTeX engine options and adding execution time limits to compilation subprocesses.

Enhancements:

  • Add -no-shell-escape options to all pdflatex and pandoc-based PDF generation paths to prevent shell command execution from LaTeX input.
  • Introduce 30-second timeouts with cleanup for PDF compilation subprocesses to avoid hangs and resource exhaustion.

Added `-no-shell-escape` flags and timeouts to `pdflatex` and `pandoc` subprocess calls
in `cli/pdf/converter.py` and `cli/generators/cover_letter_generator.py` to prevent
Remote Code Execution (RCE) via `\write18` and mitigate Denial of Service (DoS)
risks from infinite compilation loops.

Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 26, 2026

Reviewer's Guide

Tightens PDF generation security by adding -no-shell-escape flags and enforcing 30s timeouts (with cleanup and failure signaling) on all pdflatex/pandoc subprocesses used by the cover letter generator and generic PDF converter.

Sequence diagram for secured pdflatex PDF compilation with timeout

sequenceDiagram
    participant Caller
    participant Converter as converter._compile_pdflatex
    participant Subprocess as subprocess.Popen

    Caller->>Converter: _compile_pdflatex(tex_path, output_path, working_dir)
    Converter->>Subprocess: Popen(["pdflatex", "-interaction=nonstopmode", "-no-shell-escape", tex_path.name])
    activate Subprocess
    alt within 30s
        Converter->>Subprocess: process.communicate(timeout=30)
        Subprocess-->>Converter: stdout, stderr
        alt process.returncode == 0 or output_path.exists()
            Converter-->>Caller: return True
        else
            Converter-->>Caller: return False
        end
    else subprocess.TimeoutExpired
        Converter->>Subprocess: process.kill()
        Subprocess-->>Converter: process.communicate()
        Converter-->>Caller: return False
    end
    deactivate Subprocess
Loading

File-Level Changes

Change Details Files
Harden pdflatex invocation in cover letter PDF compilation with shell-escape disabling and timeout handling.
  • Extend pdflatex argument list to include -no-shell-escape to prevent LaTeX from spawning shell commands.
  • Wrap process.communicate in a 30-second timeout, killing the process and returning False on TimeoutExpired.
  • Preserve existing behavior of treating either a zero return code or presence of the expected output PDF as success.
cli/generators/cover_letter_generator.py
Harden pandoc-based PDF fallback in cover letter PDF generation with shell-escape disabling and timeout handling.
  • Update pandoc invocation to add --pdf-engine-opt=-no-shell-escape alongside the existing xelatex engine selection.
  • Introduce a 30-second communicate timeout with process.kill and False return on TimeoutExpired to avoid unbounded runs.
  • Keep success condition based on return code or presence of the output file unchanged.
cli/generators/cover_letter_generator.py
Apply the same security and robustness measures to pdflatex and pandoc calls in the generic PDF converter.
  • Add -no-shell-escape to the pdflatex command used in the converter’s _compile_pdflatex helper and enforce a 30-second timeout with kill-and-fail on TimeoutExpired.
  • Extend the converter’s pandoc invocation to include --pdf-engine-opt=-no-shell-escape and wrap communicate with a 30-second timeout and cleanup.
  • Maintain existing success semantics (return True when return code is zero or output file exists, otherwise False).
cli/pdf/converter.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The timeout handling and process cleanup logic is duplicated across all four subprocess calls; consider extracting a small helper (e.g., run_with_timeout(args, cwd) returning success/False) to centralize this behavior and reduce future drift.
  • These code paths use subprocess.Popen(...).communicate() immediately and never call check=True, so catching subprocess.CalledProcessError is ineffective; you can either switch to subprocess.run(..., check=True, timeout=30) or adjust the exception handling to match the current usage.
  • Since a timeout is now treated as a hard failure (return False), it may be useful to at least log or propagate that a timeout occurred (using the captured stdout/stderr), so callers or operators can distinguish timeouts from normal compilation failures.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The timeout handling and process cleanup logic is duplicated across all four subprocess calls; consider extracting a small helper (e.g., `run_with_timeout(args, cwd)` returning success/False) to centralize this behavior and reduce future drift.
- These code paths use `subprocess.Popen(...).communicate()` immediately and never call `check=True`, so catching `subprocess.CalledProcessError` is ineffective; you can either switch to `subprocess.run(..., check=True, timeout=30)` or adjust the exception handling to match the current usage.
- Since a timeout is now treated as a hard failure (`return False`), it may be useful to at least log or propagate that a timeout occurred (using the captured `stdout`/`stderr`), so callers or operators can distinguish timeouts from normal compilation failures.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant