Skip to content

fix: Use Path.is_relative_to() for path traversal validation#20

Merged
KHAEntertainment merged 3 commits intomasterfrom
fix/issue-12-path-validation
Mar 27, 2026
Merged

fix: Use Path.is_relative_to() for path traversal validation#20
KHAEntertainment merged 3 commits intomasterfrom
fix/issue-12-path-validation

Conversation

@KHAEntertainment
Copy link
Copy Markdown
Owner

@KHAEntertainment KHAEntertainment commented Mar 22, 2026

Summary

  • Replace try/except ValueError pattern with Path.is_relative_to() for cleaner path validation in _resolve_restore_path()
  • Added comprehensive tests for path traversal prevention

Changes

  • src/ocbs/core.py: Use is_relative_to() instead of relative_to() with exception handling
  • tests/test_core.py: Added TestResolveRestorePath class with 7 test cases covering normal paths, .openclaw prefix stripping, absolute path rejection, empty path rejection, path traversal attempts, and symlink escape prevention
  • pyproject.toml: Fixed TOML syntax for optional dependencies (pre-existing issue)
  • src/ocbs/serve.py & src/ocbs/skill.py: Fixed import errors (pre-existing broken code)

Security Fix

Fixes path traversal vulnerability where a malicious database could supply paths like ../etc/passwd to escape the target directory during restore operations.

Closes #12

Summary by CodeRabbit

  • New Features

    • Restore URLs can now be generated with configurable host and port.
  • Chores

    • Automatic background restore server initialization removed; servers must be started/stopped explicitly.
    • Restore messages simplified to reflect manual server usage.
    • Internal cleanup and stricter path validation to improve error handling and security.
  • Tests

    • Added tests covering path resolution and escape/symlink security checks.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 22, 2026

Warning

Rate limit exceeded

@KHAEntertainment has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 23 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 23 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 75348729-25d3-49db-962a-260a46a43cec

📥 Commits

Reviewing files that changed from the base of the PR and between 8699b6a and d5ce46f.

📒 Files selected for processing (1)
  • src/ocbs/serve.py

Walkthrough

Refactors restore-serving logic to remove global server state and introduce explicit URL generation; hardens path resolution by replacing a relative_to-based guard with explicit Path.is_relative_to checks; cleans up imports; and adjusts pyproject optional-dependency format for clawhub. Added tests validating path-resolution behavior.

Changes

Cohort / File(s) Summary
Path validation & tests
src/ocbs/core.py, tests/test_core.py
Replaced use of Path.relative_to(...)/try-except with Path.is_relative_to(...) in _resolve_restore_path(). Added tests (TestResolveRestorePath) covering relative paths, .openclaw/ prefix stripping, absolute/empty/traversal rejection, and symlink-escape cases.
Serve module refactor
src/ocbs/serve.py
Removed module-level global server and its lazy-start wrapper; deleted token-based serve record creation. Added generate_restore_url(checkpoint_id, port=18790, host='localhost') and changed format_restore_message(...) to use the checkpoint-based /restore?checkpoint=... URL. Kept start_restore_server(...) updated to return an HTTPServer.
Import cleanup
src/ocbs/skill.py
Removed duplicate/unused imports (OCBSCore, BackupScope re-import and RestorePageServer); remaining imports unchanged in behavior.
Project config
pyproject.toml
Changed clawhub under [project.optional-dependencies] from a single string to a list: clawhub = "clawhub>=0.1.0"clawhub = ["clawhub>=0.1.0"].

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • feat: add CLI progress tracker #21 — Very similar changes across the same files (pyproject, core._resolve_restore_path, serve.py refactor, skill import cleanup, and matching tests).
  • sync: S3 workspace dev head → master #4 — Overlapping edits to restore-serving and path-resolution logic (serve URL/token handling and core path validation).
  • KHAEntertainment/OCBS#something — Related prior PR that adjusted restore URL/token semantics and server lifecycle (investigate for history of the serve API changes).

Poem

🛡️ Paths once prone to roam and stray,

now checked at gates and bade to stay.
The global server gently fades,
URLs formed by clearer ways.
Tests sing back each guarded door—restored, secure, and more.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the primary change: replacing try/except ValueError patterns with Path.is_relative_to() for path validation in _resolve_restore_path().
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #12: path validation using is_relative_to(), rejection of absolute/traversal paths, symlink escape prevention, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed The PR includes auxiliary fixes to pyproject.toml syntax and import corrections in serve.py/skill.py that directly enable the core security fix and tests to function properly.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-12-path-validation

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/ocbs/serve.py (4)

100-335: ⚠️ Potential issue | 🔴 Critical

Multiple orphaned methods reference self outside any class.

Methods like _get_checkpoint_info, _validate_token, _mark_proceeded, _write_proceed_notification, _send_webhook_notification, get_pending_proceed_notifications, clear_proceed_notification, _mark_used, _mark_restored, get_active_serves, serve_checkpoint, get_restore_url, _get_html_page, do_GET, do_POST all reference self but aren't inside a class definition.

Additionally, these methods reference undefined names:

  • secrets (line 61)
  • datetime, timedelta (lines 66-67)
  • sqlite3 (multiple locations)
  • json (line 212)
  • os (line 220)
  • html_module (line 357)
  • server_instance (lines 736, 744, 747)

This entire section appears to be remnants of a class that was incompletely removed during refactoring.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ocbs/serve.py` around lines 100 - 335, These functions were left as
instance methods but are not inside any class and reference undefined names;
restore them into the original request/serve class (e.g., place
_get_checkpoint_info, _validate_token, _mark_proceeded,
_write_proceed_notification, _send_webhook_notification,
get_pending_proceed_notifications, clear_proceed_notification, _mark_used,
_mark_restored, get_active_serves, serve_checkpoint, get_restore_url,
_get_html_page, do_GET, do_POST back into the OCBS request/serve class so `self`
is valid, or alternatively convert them to module-level functions and remove
`self` usages; also add the missing imports (import secrets, from datetime
import datetime,timedelta, import sqlite3, import json, import os, and ensure
html_module is defined or replaced with the correct html library) and either
define/replace the undefined server_instance references or remove them; update
function signatures and any references accordingly so the code compiles and
runtime names are defined.

838-841: ⚠️ Potential issue | 🔴 Critical

Critical: detect_connection_type is called but never defined.

The __main__ block calls detect_connection_type() which doesn't exist in this file or any visible import. Running this file directly will fail with NameError.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ocbs/serve.py` around lines 838 - 841, The __main__ block calls
detect_connection_type() which is undefined; either add a proper implementation
of the function or import it from the module that provides it. Implement a
function named detect_connection_type that returns a tuple (conn_type, host) and
handles errors (or raise a clear exception), or add an import like "from
<module> import detect_connection_type" and ensure the symbol exists; update the
main block to catch exceptions from detect_connection_type and print a helpful
error if detection fails.

815-835: ⚠️ Potential issue | 🔴 Critical

Add missing imports for threading and define or import RestoreHandler.

The start_restore_server function uses RestoreHandler (line 832) and threading (line 833), but neither is imported or defined in this file. When called, this function will immediately crash with a NameError—like trying to drive a car with parts missing from the engine.

You'll need to:

  1. Import the threading module at the top of the file
  2. Either define RestoreHandler in this file or import it from wherever it's defined
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ocbs/serve.py` around lines 815 - 835, The start_restore_server function
references threading and RestoreHandler but neither is defined or imported,
causing a NameError; add "import threading" near the other imports and either
define RestoreHandler in this module or import it from its source (e.g., from
mymodule.handlers import RestoreHandler) so start_restore_server can construct
HTTPServer((bind_host, port), RestoreHandler) and spawn the thread; ensure the
import name matches the RestoreHandler class used in start_restore_server.

59-98: ⚠️ Potential issue | 🔴 Critical

Multiple critical runtime errors: undefined references and incorrectly nested methods.

The code has severe structural problems that will cause NameError and NameError exceptions:

  • Lines 59-72: Methods _generate_token and _create_serve_record are indented inside the get_tailscale_ip() function, making them inaccessible local functions that reference self—which doesn't exist in that scope. This looks like leftover code from a refactoring that removed a RestorePageServer class.

  • Line 832: References RestoreHandler class that's never defined or imported anywhere in the codebase.

  • Line 840: Calls detect_connection_type() function that doesn't exist.

  • Missing imports: The code uses secrets, datetime/timedelta, sqlite3, threading, and html modules without importing them, causing NameError at runtime.

  • Format mismatch: format_restore_message() returns a placeholder message without actually generating or including a URL, breaking the user-facing output that the CLI and skill modules expect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ocbs/serve.py` around lines 59 - 98, The two methods _generate_token and
_create_serve_record are accidentally nested inside get_tailscale_ip and
reference self; move them out to be methods of the appropriate class (restore
server class) or top-level functions as originally intended so they can access
self and _active_tokens correctly, and ensure they use datetime/timedelta,
secrets, and sqlite3 which you must import; define or import RestoreHandler and
implement or import detect_connection_type where they are referenced (lines
around 832/840) so calls resolve, and update format_restore_message to produce
and return the actual restore URL (include html escaping via the html module if
needed) instead of the placeholder text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ocbs/serve.py`:
- Line 9: The file currently only imports HTTPServer but is missing several
required imports used elsewhere (add imports at the top alongside HTTPServer):
secrets; from datetime import datetime, timedelta; sqlite3; json; os; import
html as html_module; and threading; update the import section so symbols like
HTTPServer and html_module and datetime/timedelta are available to the rest of
serve.py.
- Around line 844-856: The formatted restore message currently omits the actual
URL; change format_restore_message to call generate_restore_url(checkpoint_id,
port, host) and include the returned URL in the multi-line message (keeping the
original reason and explanatory text), so callers (skill.py and cli.py) that use
format_restore_message get a usable restore link; ensure signature and default
port/host are preserved so imports of generate_restore_url remain consistent.

---

Outside diff comments:
In `@src/ocbs/serve.py`:
- Around line 100-335: These functions were left as instance methods but are not
inside any class and reference undefined names; restore them into the original
request/serve class (e.g., place _get_checkpoint_info, _validate_token,
_mark_proceeded, _write_proceed_notification, _send_webhook_notification,
get_pending_proceed_notifications, clear_proceed_notification, _mark_used,
_mark_restored, get_active_serves, serve_checkpoint, get_restore_url,
_get_html_page, do_GET, do_POST back into the OCBS request/serve class so `self`
is valid, or alternatively convert them to module-level functions and remove
`self` usages; also add the missing imports (import secrets, from datetime
import datetime,timedelta, import sqlite3, import json, import os, and ensure
html_module is defined or replaced with the correct html library) and either
define/replace the undefined server_instance references or remove them; update
function signatures and any references accordingly so the code compiles and
runtime names are defined.
- Around line 838-841: The __main__ block calls detect_connection_type() which
is undefined; either add a proper implementation of the function or import it
from the module that provides it. Implement a function named
detect_connection_type that returns a tuple (conn_type, host) and handles errors
(or raise a clear exception), or add an import like "from <module> import
detect_connection_type" and ensure the symbol exists; update the main block to
catch exceptions from detect_connection_type and print a helpful error if
detection fails.
- Around line 815-835: The start_restore_server function references threading
and RestoreHandler but neither is defined or imported, causing a NameError; add
"import threading" near the other imports and either define RestoreHandler in
this module or import it from its source (e.g., from mymodule.handlers import
RestoreHandler) so start_restore_server can construct HTTPServer((bind_host,
port), RestoreHandler) and spawn the thread; ensure the import name matches the
RestoreHandler class used in start_restore_server.
- Around line 59-98: The two methods _generate_token and _create_serve_record
are accidentally nested inside get_tailscale_ip and reference self; move them
out to be methods of the appropriate class (restore server class) or top-level
functions as originally intended so they can access self and _active_tokens
correctly, and ensure they use datetime/timedelta, secrets, and sqlite3 which
you must import; define or import RestoreHandler and implement or import
detect_connection_type where they are referenced (lines around 832/840) so calls
resolve, and update format_restore_message to produce and return the actual
restore URL (include html escaping via the html module if needed) instead of the
placeholder text.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2658512-d032-4974-98cd-fdd4e6f8fe9c

📥 Commits

Reviewing files that changed from the base of the PR and between 34626a9 and 1819cb0.

📒 Files selected for processing (5)
  • pyproject.toml
  • src/ocbs/core.py
  • src/ocbs/serve.py
  • src/ocbs/skill.py
  • tests/test_core.py
💤 Files with no reviewable changes (1)
  • src/ocbs/skill.py

Comment thread src/ocbs/serve.py
Comment thread src/ocbs/serve.py Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 2 unresolved review comments.

Files modified:

  • src/ocbs/serve.py

Commit: 8699b6a80f0463b397e5acd3ef4fd0a7b7347932

The changes have been pushed to the fix/issue-12-path-validation branch.

Time taken: 3m 44s

coderabbitai bot and others added 2 commits March 27, 2026 00:20
Fixed 1 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@KHAEntertainment KHAEntertainment merged commit b19c03d into master Mar 27, 2026
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.

security: Restore code trusts DB-supplied file_path without validation

1 participant