From 216d526206979b27d38ff6d66c350dc49c3e3f10 Mon Sep 17 00:00:00 2001 From: Anshika0998 Date: Wed, 17 Jun 2026 00:11:16 +0530 Subject: [PATCH 1/4] Fix scan_directory sandbox validation --- .../connector/policy.py | 23 +++++++++++-------- .../connector/workflows/weekly_report.py | 8 ++----- .../orchestrator/planner.py | 6 +++++ .../orchestrator/policy.py | 14 +++++++++++ 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/openclaw/fetchai-openclaw-orchestrator/connector/policy.py b/openclaw/fetchai-openclaw-orchestrator/connector/policy.py index 4234254..2e46d40 100644 --- a/openclaw/fetchai-openclaw-orchestrator/connector/policy.py +++ b/openclaw/fetchai-openclaw-orchestrator/connector/policy.py @@ -38,12 +38,9 @@ _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 +73,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..1f0559b 100644 --- a/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py +++ b/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py @@ -40,14 +40,10 @@ 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() if not root.exists(): diff --git a/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py b/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py index 107ab19..15dced9 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..38ee257 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 @@ -82,10 +83,23 @@ def check_plan(self, plan: TaskPlan) -> RejectionReason | None: return RejectionReason.POLICY_VIOLATION for step in plan.steps: + if step.action not in self.allowed_actions: 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: From 039f613f2081c5df4d926084e82ea883c50149a3 Mon Sep 17 00:00:00 2001 From: Anshika0998 Date: Thu, 18 Jun 2026 22:10:09 +0530 Subject: [PATCH 2/4] fix: address review feedback in policy validation --- .../orchestrator/policy.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py b/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py index 38ee257..ac82630 100644 --- a/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py +++ b/openclaw/fetchai-openclaw-orchestrator/orchestrator/policy.py @@ -83,23 +83,21 @@ def check_plan(self, plan: TaskPlan) -> RejectionReason | None: return RejectionReason.POLICY_VIOLATION for step in plan.steps: - if step.action not in self.allowed_actions: 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 + 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: From c979a291d6d13da413b38c61d7266fd4c831dd92 Mon Sep 17 00:00:00 2001 From: Anshika0998 Date: Sat, 20 Jun 2026 15:26:21 +0530 Subject: [PATCH 3/4] docs: add changelog entries for scan_directory fix --- CHANGELOG.md | 4 +++- contributors/CHANGELOG.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 1c95711..28858cf 100644 --- a/contributors/CHANGELOG.md +++ b/contributors/CHANGELOG.md @@ -9,4 +9,6 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - `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 \ No newline at end of file +- `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) \ No newline at end of file From ca116ff2779b6e2d730b7740ddab259d6732580d Mon Sep 17 00:00:00 2001 From: Anshika0998 Date: Sun, 21 Jun 2026 22:42:01 +0530 Subject: [PATCH 4/4] style: apply formatting fixes --- openclaw/fetchai-openclaw-orchestrator/connector/policy.py | 3 +-- .../connector/workflows/weekly_report.py | 2 +- openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openclaw/fetchai-openclaw-orchestrator/connector/policy.py b/openclaw/fetchai-openclaw-orchestrator/connector/policy.py index 2e46d40..99ab473 100644 --- a/openclaw/fetchai-openclaw-orchestrator/connector/policy.py +++ b/openclaw/fetchai-openclaw-orchestrator/connector/policy.py @@ -39,8 +39,7 @@ DEFAULT_ALLOWED_PATHS: list[str] = [ str(Path(_DEMO_DIR).resolve()), -] # restricted sandbox - +] # restricted sandbox # --------------------------------------------------------------------------- diff --git a/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py b/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py index 1f0559b..6c08f7c 100644 --- a/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py +++ b/openclaw/fetchai-openclaw-orchestrator/connector/workflows/weekly_report.py @@ -43,7 +43,7 @@ def scan_directory(params: dict[str, Any]) -> dict[str, Any]: raw_path = _DEFAULT_SCAN_PATH # Sanitise: resolve relative to project root, never expose home dir - + root = Path(raw_path).resolve() if not root.exists(): diff --git a/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py b/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py index 15dced9..cd9fd1b 100644 --- a/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py +++ b/openclaw/fetchai-openclaw-orchestrator/orchestrator/planner.py @@ -343,7 +343,7 @@ def plan_objective(objective: str) -> TaskPlan: """ # Try LLM first plan = _plan_with_llm(objective) - + if plan is not None: for step in plan.steps: