This commit is contained in:
Efe Büken 2026-05-30 18:34:52 +08:00 • committed by GitHub
commit 915746f28e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 47 additions and 18 deletions

25
SECURITY.md Normal file
View file

@ -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.

View file

@ -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": {

View file

@ -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

View file

@ -533,7 +533,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.
@ -559,9 +559,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()
@ -624,9 +625,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.
@ -644,9 +645,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

View file

@ -64,16 +64,16 @@ class MCPAgentExecutionEvent(BaseTelemetryEvent):
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,
@ -85,7 +85,6 @@ class MCPAgentExecutionEvent(BaseTelemetryEvent):
"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,

View file

@ -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