Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Security Policy

## Reporting Vulnerabilities

If you discover a security vulnerability, please report it responsibly by opening a private security advisory on this repository. Do **not** open a public issue.

## Security Considerations for Users

OpenSpace is a powerful agent framework that can execute shell commands, run arbitrary code, and connect to external services. Users should be aware of the following:

### Telemetry

Telemetry is **disabled by default** as of this PR. If you opt in by setting `MCP_USE_ANONYMIZED_TELEMETRY=true`, be aware that execution metadata (model names, tool usage counts, timing) is sent to PostHog and Scarf. Query text and response text are **never** transmitted regardless of this setting.

### Cloud Skills

Cloud skill search and auto-import are **disabled by default** (`search_scope="local"`). If you enable cloud search (`search_scope="all"`), downloaded skills are not sandboxed or signature-verified. Only enable this in trusted environments.

### Host Config Auto-Detection

OpenSpace reads host agent configs (`~/.openclaw/openclaw.json`, `~/.nanobot/config.json`) to auto-detect LLM credentials. It only reads from the explicitly scoped `openspace` env blocks — not top-level or unrelated configuration sections.

### Shell Execution

The grounding engine can execute shell commands. The `config_security.json` defines blocked command lists, but this is a denylist approach. For production deployments, enable sandboxing (`sandbox_enabled: true`) and review the security policy configuration.
2 changes: 1 addition & 1 deletion openspace/config/config_security.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"darwin": ["diskutil", "dd", "pfctl", "launchctl", "killall"],
"windows": ["del", "format", "rd", "rmdir", "/s", "/q", "taskkill", "/f"]
},
"sandbox_enabled": false
"sandbox_enabled": true
},
"backend": {
"shell": {
Expand Down
11 changes: 7 additions & 4 deletions openspace/host_detection/openclaw.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,16 @@ def read_openclaw_skill_env(skill_name: str = "openspace") -> Dict[str, str]:
def get_openclaw_openai_api_key() -> Optional[str]:
"""Get OpenAI API key from OpenClaw config.

Checks ``skills.entries.openspace.env.OPENAI_API_KEY`` first,
then any top-level env vars in the config.
Only reads from the explicitly scoped ``skills.entries.openspace.env``
block. Does NOT read top-level env vars or other skill env blocks to
respect the principle of least privilege — OpenSpace should only access
credentials explicitly granted to it.

Returns the key string, or None.
"""
env = _get_openclaw_env("openspace")
key = _coerce_env_value(env.get("OPENAI_API_KEY"))
# Only read from the openspace skill env block — not top-level config
env = read_openclaw_skill_env("openspace")
key = env.get("OPENAI_API_KEY", "").strip()
if key:
logger.debug("Using OpenAI API key from OpenClaw skill env config")
return key
Expand Down
18 changes: 10 additions & 8 deletions openspace/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ async def execute_task(
workspace_dir: str | None = None,
max_iterations: int | None = None,
skill_dirs: list[str] | None = None,
search_scope: str = "all",
search_scope: str = "local",
) -> str:
"""Execute a task with OpenSpace's full grounding engine.

Expand All @@ -552,9 +552,10 @@ async def execute_task(
on every call to discover skills created since the last
invocation.
search_scope: Skill search scope before execution.
"all" (default) — local + cloud; falls back to local
if no API key is configured.
"local" — local SkillRegistry only (fast, no cloud).
"local" (default) — local SkillRegistry only (fast, no cloud).
"all" — local + cloud; falls back to local
if no API key is configured. Use with caution: cloud
skills are unverified and auto-imported without review.
"""
try:
openspace = await _get_openspace()
Expand Down Expand Up @@ -602,9 +603,9 @@ async def execute_task(
@mcp.tool()
async def search_skills(
query: str,
source: str = "all",
source: str = "local",
limit: int = 20,
auto_import: bool = True,
auto_import: bool = False,
) -> str:
"""Search skills across local registry and cloud community.

Expand All @@ -622,9 +623,10 @@ async def search_skills(

Args:
query: Search query text (natural language or keywords).
source: "all" (cloud + local), "local", or "cloud". Default: "all".
source: "local" (default), "all" (cloud + local), or "cloud".
limit: Maximum results to return (default: 20).
auto_import: Auto-download top public cloud skills (default: True).
auto_import: Auto-download top public cloud skills (default: False).
Enable explicitly to allow unverified cloud skill imports.
"""
try:
from openspace.cloud.search import hybrid_search_skills
Expand Down
7 changes: 3 additions & 4 deletions openspace/utils/telemetry/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,16 @@ def properties(self) -> dict[str, Any]:
return {
# Core execution info
"execution_method": self.execution_method,
"query": self.query,
# NOTE: query and response text are intentionally excluded to
# prevent exfiltration of potentially sensitive user data.
# Only lengths are reported for aggregate analytics.
"query_length": len(self.query),
"success": self.success,
# Agent configuration
"model_provider": self.model_provider,
"model_name": self.model_name,
"server_count": self.server_count,
"server_identifiers": self.server_identifiers,
"total_tools_available": self.total_tools_available,
"tools_available_names": self.tools_available_names,
"max_steps_configured": self.max_steps_configured,
"memory_enabled": self.memory_enabled,
"use_server_manager": self.use_server_manager,
Expand All @@ -85,7 +85,6 @@ def properties(self) -> dict[str, Any]:
"steps_taken": self.steps_taken,
"tools_used_count": self.tools_used_count,
"tools_used_names": self.tools_used_names,
"response": self.response,
"response_length": len(self.response) if self.response else None,
"execution_time_ms": self.execution_time_ms,
"error_type": self.error_type,
Expand Down
2 changes: 1 addition & 1 deletion openspace/utils/telemetry/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class Telemetry:
_curr_user_id = None

def __init__(self):
telemetry_disabled = os.getenv("MCP_USE_ANONYMIZED_TELEMETRY", "true").lower() == "false"
telemetry_disabled = os.getenv("MCP_USE_ANONYMIZED_TELEMETRY", "false").lower() == "false"

if telemetry_disabled:
self._posthog_client = None
Expand Down