Skip to content

fix: address CodeQL path injection alerts in runbook routes#152

Merged
jerry609 merged 2 commits into
masterfrom
fix/runbook-path-sanitization-codeql
Mar 2, 2026
Merged

fix: address CodeQL path injection alerts in runbook routes#152
jerry609 merged 2 commits into
masterfrom
fix/runbook-path-sanitization-codeql

Conversation

@jerry609

@jerry609 jerry609 commented Mar 2, 2026

Copy link
Copy Markdown
Owner

What

  • harden user path normalization in runbook directory endpoints
  • enforce allow-prefix checks before accepting user paths
  • reuse robust prefix containment helper (Path.is_relative_to)
  • add unit coverage for allowed-dirs accept/reject behavior

Why

CodeQL flagged uncontrolled data in path expressions for:

  • /api/runbook/project-dir/prepare
  • /api/runbook/allowed-dirs

This patch narrows accepted inputs to configured allowed prefixes and blocks malformed paths before use.

Validation

  • python -m py_compile src/paperbot/api/routes/runbook.py tests/unit/test_runbook_project_dir_prepare_route.py
  • pytest -q tests/unit/test_runbook_project_dir_prepare_route.py (segfaults in current local env; existing machine issue)

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced directory path validation and normalization in the runbook API for more consistent and secure handling of user-supplied paths.
    • Improved input validation with tighter policy enforcement on allowed directories.
  • Tests

    • Added test coverage for directory validation in the runbook allowed-dirs endpoint, including verification of path acceptance and rejection logic.

Copilot AI review requested due to automatic review settings March 2, 2026 08:35
@vercel

vercel Bot commented Mar 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
paper-bot Ready Ready Preview, Comment Mar 2, 2026 8:41am

@coderabbitai

coderabbitai Bot commented Mar 2, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc0d8b8 and 126a69d.

📒 Files selected for processing (2)
  • src/paperbot/api/routes/runbook.py
  • tests/unit/test_runbook_project_dir_prepare_route.py

📝 Walkthrough

Walkthrough

Introduces two private helper functions to centralize path normalization and validation logic in the runbook API: _is_under_prefix() for consistent prefix checking and _normalize_user_directory() for validating user-supplied paths. Refactors existing path handling throughout the module to use these helpers.

Changes

Cohort / File(s) Summary
Path Normalization Helpers
src/paperbot/api/routes/runbook.py
Introduces _is_under_prefix() to handle normalized prefix checks and _normalize_user_directory() to validate, normalize, and enforce allowlist policies for user directories. Refactors _allowed_workdir, prepare_project_dir, and add_allowed_dir to use these new helpers, centralizing validation logic and tightening error handling.
Test Coverage
tests/unit/test_runbook_project_dir_prepare_route.py
Adds two new tests: validates that existing directories under the current working directory are accepted (returns 200 with resolved path), and verifies that directories outside allowed prefixes are rejected (returns 403 with "not allowed" detail).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Helpers now organize the way,
Paths validated, checked today,
Prefixes normalized so clean,
The clearest routes we've ever seen!
Refactored burrows, hopping bright!

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/runbook-path-sanitization-codeql

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 and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses CodeQL path injection alerts by significantly enhancing the security and validation of user-supplied directory paths within the runbook API routes. It introduces a new, centralized path normalization and validation function that strictly enforces allowed prefixes and handles various edge cases, preventing potential malicious path manipulations.

Highlights

  • Path Normalization and Validation: Implemented robust user path normalization in runbook directory endpoints to prevent path injection vulnerabilities.
  • Allow-Prefix Enforcement: Enforced strict allow-prefix checks for all user-supplied paths, ensuring that operations are confined to designated safe directories.
  • Code Reusability: Introduced and reused a new helper function, _is_under_prefix, for consistent and reliable prefix containment checks across the codebase.
  • Unit Test Coverage: Added comprehensive unit test coverage to validate the accept/reject behavior of allowed-directories, ensuring the new security measures function as intended.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/paperbot/api/routes/runbook.py
    • Introduced _is_under_prefix helper function to check if a path is under a given prefix.
    • Updated _allowed_workdir to utilize the new _is_under_prefix helper for improved clarity and robustness.
    • Added _normalize_user_directory function to centralize user-supplied path normalization, ~ expansion, absolute path resolution, and allow-prefix enforcement, raising HTTPException on invalid or disallowed paths.
    • Refactored prepare_project_dir endpoint to use _normalize_user_directory for path validation.
    • Refactored add_allowed_dir endpoint to use _normalize_user_directory for path validation.
  • tests/unit/test_runbook_project_dir_prepare_route.py
    • Added test_add_allowed_dir_accepts_existing_directory_under_cwd to verify that add_allowed_dir correctly accepts valid paths.
    • Added test_add_allowed_dir_rejects_outside_allowed_prefixes to ensure add_allowed_dir rejects paths outside the configured allowed prefixes.
Activity
  • No human activity (comments, reviews, etc.) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Comment thread src/paperbot/api/routes/runbook.py Fixed
Comment thread src/paperbot/api/routes/runbook.py Fixed

Copilot AI 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.

Pull request overview

This PR hardens path handling in the runbook directory-related API routes to address CodeQL path-injection concerns by normalizing user-supplied paths and enforcing allow-prefix containment checks, and adds unit tests for allowlist mutation behavior.

Changes:

  • Added shared helpers to normalize user-supplied directory paths and enforce “must be under allowed prefixes” policy.
  • Updated runbook endpoints to use the new normalization/containment logic.
  • Added unit tests covering accept/reject behavior for the runtime allowed-dirs mutation endpoint.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/paperbot/api/routes/runbook.py Adds prefix containment + user path normalization helper and applies it to runbook directory endpoints.
tests/unit/test_runbook_project_dir_prepare_route.py Adds tests for /runbook/allowed-dirs allowlist mutation accept/reject behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/paperbot/api/routes/runbook.py Outdated
try:
resolved = path.resolve(strict=False)
normalized_prefix = prefix.resolve(strict=False)
return resolved == normalized_prefix or resolved.is_relative_to(normalized_prefix)

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

_is_under_prefix() uses Path.is_relative_to(), but the project supports Python >=3.8 (pyproject.toml). is_relative_to doesn’t exist on 3.8, so this will be caught by the broad except and return False, effectively making all allowlist checks fail on 3.8. Consider replacing this with a 3.8-compatible containment check (e.g., resolved.relative_to(normalized_prefix) in a try/except) while still using resolved paths to avoid traversal/symlink issues.

Suggested change
return resolved == normalized_prefix or resolved.is_relative_to(normalized_prefix)
if resolved == normalized_prefix:
return True
try:
# Python 3.8-compatible alternative to Path.is_relative_to()
resolved.relative_to(normalized_prefix)
return True
except ValueError:
return False

Copilot uses AI. Check for mistakes.
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("PAPERBOT_RUNBOOK_ALLOWLIST_MUTATION", "true")

outside = Path.home().parent / "paperbot-unsafe"

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

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

This test creates a directory under Path.home().parent (often /home/...), which is frequently not writable in CI and can cause permission-related test failures and leave artifacts outside the pytest temp dir. Since the route should reject disallowed paths before checking existence, you can avoid mkdir() entirely, or choose a disallowed path that remains within writable test temp locations.

Suggested change
outside = Path.home().parent / "paperbot-unsafe"
outside = tmp_path.parent / "paperbot-unsafe"

Copilot uses AI. Check for mistakes.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request effectively addresses CodeQL path injection alerts in the runbook routes by implementing robust path normalization and validation. It centralizes security logic with _normalize_user_directory and _is_under_prefix helpers, ensuring user-supplied paths are strictly validated against an allowlist using Path.resolve(strict=False) and Path.is_relative_to(). I've provided suggestions to improve code clarity and simplify new logic, specifically by removing redundant try/except blocks and streamlining path construction. The new unit tests are a great addition, improving coverage for this security-sensitive code, and no new security vulnerabilities were introduced.

Comment on lines +164 to +165
suffix = normalized[len(prefix_str):].lstrip("/\\")
candidate = (prefix / suffix).resolve(strict=False) if suffix else prefix

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic for reconstructing the candidate path from prefix and suffix is complex and seems redundant. Since normalized is already a canonical path string (from resolve() on line 157), you can create the Path object directly from it. This improves readability without changing the behavior.

Suggested change
suffix = normalized[len(prefix_str):].lstrip("/\\")
candidate = (prefix / suffix).resolve(strict=False) if suffix else prefix
candidate = Path(normalized)

Comment on lines 186 to +189
try:
resolved = requested.resolve(strict=False)
except Exception:
raise HTTPException(status_code=400, detail="invalid project_dir")

if not _allowed_workdir(resolved):
raise HTTPException(status_code=403, detail="project_dir is not allowed")
resolved = _normalize_user_directory(body.project_dir, field_name="project_dir")
except HTTPException:
raise

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This try...except HTTPException: raise block is redundant. The code will have the same behavior without it, as an HTTPException raised from _normalize_user_directory will propagate up the call stack anyway. Removing this block will make the code cleaner.

    resolved = _normalize_user_directory(body.project_dir, field_name="project_dir")

Comment on lines 234 to +237
try:
resolved = Path(body.directory).expanduser().resolve()
except Exception:
raise HTTPException(status_code=400, detail="invalid directory path")
resolved = _normalize_user_directory(body.directory, field_name="directory")
except HTTPException:
raise

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This try...except HTTPException: raise block is redundant, similar to the one in prepare_project_dir. The code will have the same behavior without it. Removing this block will improve code clarity.

    resolved = _normalize_user_directory(body.directory, field_name="directory")

@jerry609
jerry609 merged commit 0d95656 into master Mar 2, 2026
9 of 10 checks passed
@jerry609
jerry609 deleted the fix/runbook-path-sanitization-codeql branch March 10, 2026 02:15
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.

3 participants