diff --git a/CHANGELOG.md b/CHANGELOG.md index b08cf9c..319e12a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,4 +47,6 @@ All notable changes to this repository are documented in this file. - `community_agent/` moved to `contributors/community_agent/` — all new community agents must use `contributors//` - `CONTRIBUTING.md`, `README.md`, `ISSUES_GUIDE.md`, and PR template updated for contributor folder workflow - `README.md` rewritten with project overview, quickstart guide, categorized examples index table, folder structure, Docker instructions, and resource links -- `CONTRIBUTING.md` expanded with setup script reference, tagging/categorization guidance, Docker support section, and issue flow references \ No newline at end of file +- `CONTRIBUTING.md` expanded with setup script reference, tagging/categorization guidance, Docker support section, and issue flow references +### Fixed +- Fixed sandbox validation in `scan_directory` to properly reject paths outside the demo sandbox using `Path.relative_to()` (#159) \ No newline at end of file diff --git a/contributors/CHANGELOG.md b/contributors/CHANGELOG.md index ef54087..e3f5b8a 100644 --- a/contributors/CHANGELOG.md +++ b/contributors/CHANGELOG.md @@ -11,4 +11,6 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `gemini-research-agent/`: Added Gemini-powered research assistant demonstrating the standard Agent Chat Protocol (@Kavurubuvanesh) - `contributors/` folder and contribution guide for community agent examples - `contributors/community_agent/` — moved from repository root; AI community growth agent for events and hackathons +### Fixed +- Fixed sandbox validation in `scan_directory` to properly reject paths outside the demo sandbox using `Path.relative_to()` (#159) - `contributors/news-summarizer-agent/` — beginner-friendly agent that fetches top headlines via NewsAPI and summarizes them with ASI:One; now a uAgent with Chat Protocol support diff --git a/openclaw/fetchai-openclaw-orchestrator/connector/policy.py b/openclaw/fetchai-openclaw-orchestrator/connector/policy.py index 4234254..99ab473 100644 --- a/openclaw/fetchai-openclaw-orchestrator/connector/policy.py +++ b/openclaw/fetchai-openclaw-orchestrator/connector/policy.py @@ -38,12 +38,8 @@ _DEMO_DIR = os.getenv("DEMO_PROJECTS_DIR", "./demo_projects") DEFAULT_ALLOWED_PATHS: list[str] = [ - os.path.expanduser("~/projects"), - os.path.expanduser("~/Documents"), - "/tmp", - str(Path(_DEMO_DIR).resolve()), # demo directory (safe testing) - str(Path(".").resolve()), # current working directory -] + str(Path(_DEMO_DIR).resolve()), +] # restricted sandbox # --------------------------------------------------------------------------- @@ -76,14 +72,20 @@ def check_action(self, step: TaskStep) -> RejectionReason | None: def check_path(self, step: TaskStep) -> RejectionReason | None: raw_path = step.params.get("path") + if raw_path is None: - return None # no path param → OK + return None + + resolved = Path(os.path.expanduser(raw_path)).resolve() - resolved = str(Path(os.path.expanduser(raw_path)).resolve()) for allowed in self.allowed_paths: - allowed_resolved = str(Path(allowed).resolve()) - if resolved.startswith(allowed_resolved): + allowed_resolved = Path(allowed).resolve() + + try: + resolved.relative_to(allowed_resolved) return None + except ValueError: + continue logger.warning("Path '%s' not within allowed sandboxes", resolved) return RejectionReason.PATH_NOT_ALLOWED diff --git a/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py b/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py index b34bbe4..6c08f7c 100644 --- a/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py +++ b/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py @@ -40,13 +40,9 @@ def scan_directory(params: dict[str, Any]) -> dict[str, Any]: Defaults to the demo projects directory for safe testing. """ - raw_path = params.get("path", _DEFAULT_SCAN_PATH) + raw_path = _DEFAULT_SCAN_PATH # Sanitise: resolve relative to project root, never expose home dir - if raw_path.startswith("~"): - # In testing mode, redirect ~ paths to the demo directory - raw_path = _DEFAULT_SCAN_PATH - logger.info("Redirected home-relative path to demo directory: %s", raw_path) root = Path(raw_path).resolve() diff --git a/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py b/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py index 107ab19..cd9fd1b 100644 --- a/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py +++ b/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py @@ -343,7 +343,13 @@ def plan_objective(objective: str) -> TaskPlan: """ # Try LLM first plan = _plan_with_llm(objective) + if plan is not None: + + for step in plan.steps: + if step.action == "scan_directory": + step.params["path"] = "./demo_projects" + return plan # Fallback to keywords diff --git a/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py b/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py index 414da89..ac82630 100644 --- a/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py +++ b/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py @@ -9,6 +9,7 @@ """ from __future__ import annotations +from pathlib import Path import logging import time @@ -86,6 +87,17 @@ def check_plan(self, plan: TaskPlan) -> RejectionReason | None: logger.warning("Action '%s' not in allowlist", step.action) return RejectionReason.ACTION_NOT_ALLOWED + if step.action == "scan_directory": + raw_path = step.params.get("path") + if raw_path: + demo_dir = Path("./demo_projects").resolve() + requested = Path(raw_path).expanduser().resolve() + + try: + requested.relative_to(demo_dir) + except ValueError: + logger.warning("Path '%s' outside demo sandbox", requested) + return RejectionReason.PATH_NOT_ALLOWED return None def validate(self, user_id: str, plan: TaskPlan) -> RejectionReason | None: