mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-10-07 02:57:50 +00:00
Merge 0711cfd7fa into d1e367d0ed
This commit is contained in:
commit
ea97eaeb76
6 changed files with 47 additions and 18 deletions
25
SECURITY.md
Normal file
25
SECURITY.md
Normal 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.
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"darwin": ["diskutil", "dd", "pfctl", "launchctl", "killall"],
|
"darwin": ["diskutil", "dd", "pfctl", "launchctl", "killall"],
|
||||||
"windows": ["del", "format", "rd", "rmdir", "/s", "/q", "taskkill", "/f"]
|
"windows": ["del", "format", "rd", "rmdir", "/s", "/q", "taskkill", "/f"]
|
||||||
},
|
},
|
||||||
"sandbox_enabled": false
|
"sandbox_enabled": true
|
||||||
},
|
},
|
||||||
"backend": {
|
"backend": {
|
||||||
"shell": {
|
"shell": {
|
||||||
|
|
|
||||||
|
|
@ -265,13 +265,16 @@ def read_openclaw_skill_env(skill_name: str = "openspace") -> Dict[str, str]:
|
||||||
def get_openclaw_openai_api_key() -> Optional[str]:
|
def get_openclaw_openai_api_key() -> Optional[str]:
|
||||||
"""Get OpenAI API key from OpenClaw config.
|
"""Get OpenAI API key from OpenClaw config.
|
||||||
|
|
||||||
Checks ``skills.entries.openspace.env.OPENAI_API_KEY`` first,
|
Only reads from the explicitly scoped ``skills.entries.openspace.env``
|
||||||
then any top-level env vars in the config.
|
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.
|
Returns the key string, or None.
|
||||||
"""
|
"""
|
||||||
env = _get_openclaw_env("openspace")
|
# Only read from the openspace skill env block — not top-level config
|
||||||
key = _coerce_env_value(env.get("OPENAI_API_KEY"))
|
env = read_openclaw_skill_env("openspace")
|
||||||
|
key = env.get("OPENAI_API_KEY", "").strip()
|
||||||
if key:
|
if key:
|
||||||
logger.debug("Using OpenAI API key from OpenClaw skill env config")
|
logger.debug("Using OpenAI API key from OpenClaw skill env config")
|
||||||
return key
|
return key
|
||||||
|
|
|
||||||
|
|
@ -533,7 +533,7 @@ async def execute_task(
|
||||||
workspace_dir: str | None = None,
|
workspace_dir: str | None = None,
|
||||||
max_iterations: int | None = None,
|
max_iterations: int | None = None,
|
||||||
skill_dirs: list[str] | None = None,
|
skill_dirs: list[str] | None = None,
|
||||||
search_scope: str = "all",
|
search_scope: str = "local",
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Execute a task with OpenSpace's full grounding engine.
|
"""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
|
on every call to discover skills created since the last
|
||||||
invocation.
|
invocation.
|
||||||
search_scope: Skill search scope before execution.
|
search_scope: Skill search scope before execution.
|
||||||
"all" (default) — local + cloud; falls back to local
|
"local" (default) — local SkillRegistry only (fast, no cloud).
|
||||||
if no API key is configured.
|
"all" — local + cloud; falls back to local
|
||||||
"local" — local SkillRegistry only (fast, no cloud).
|
if no API key is configured. Use with caution: cloud
|
||||||
|
skills are unverified and auto-imported without review.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
openspace = await _get_openspace()
|
openspace = await _get_openspace()
|
||||||
|
|
@ -624,9 +625,9 @@ async def execute_task(
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
async def search_skills(
|
async def search_skills(
|
||||||
query: str,
|
query: str,
|
||||||
source: str = "all",
|
source: str = "local",
|
||||||
limit: int = 20,
|
limit: int = 20,
|
||||||
auto_import: bool = True,
|
auto_import: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Search skills across local registry and cloud community.
|
"""Search skills across local registry and cloud community.
|
||||||
|
|
||||||
|
|
@ -644,9 +645,10 @@ async def search_skills(
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query: Search query text (natural language or keywords).
|
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).
|
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:
|
try:
|
||||||
from openspace.cloud.search import hybrid_search_skills
|
from openspace.cloud.search import hybrid_search_skills
|
||||||
|
|
|
||||||
|
|
@ -64,16 +64,16 @@ class MCPAgentExecutionEvent(BaseTelemetryEvent):
|
||||||
return {
|
return {
|
||||||
# Core execution info
|
# Core execution info
|
||||||
"execution_method": self.execution_method,
|
"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),
|
"query_length": len(self.query),
|
||||||
"success": self.success,
|
"success": self.success,
|
||||||
# Agent configuration
|
# Agent configuration
|
||||||
"model_provider": self.model_provider,
|
"model_provider": self.model_provider,
|
||||||
"model_name": self.model_name,
|
"model_name": self.model_name,
|
||||||
"server_count": self.server_count,
|
"server_count": self.server_count,
|
||||||
"server_identifiers": self.server_identifiers,
|
|
||||||
"total_tools_available": self.total_tools_available,
|
"total_tools_available": self.total_tools_available,
|
||||||
"tools_available_names": self.tools_available_names,
|
|
||||||
"max_steps_configured": self.max_steps_configured,
|
"max_steps_configured": self.max_steps_configured,
|
||||||
"memory_enabled": self.memory_enabled,
|
"memory_enabled": self.memory_enabled,
|
||||||
"use_server_manager": self.use_server_manager,
|
"use_server_manager": self.use_server_manager,
|
||||||
|
|
@ -85,7 +85,6 @@ class MCPAgentExecutionEvent(BaseTelemetryEvent):
|
||||||
"steps_taken": self.steps_taken,
|
"steps_taken": self.steps_taken,
|
||||||
"tools_used_count": self.tools_used_count,
|
"tools_used_count": self.tools_used_count,
|
||||||
"tools_used_names": self.tools_used_names,
|
"tools_used_names": self.tools_used_names,
|
||||||
"response": self.response,
|
|
||||||
"response_length": len(self.response) if self.response else None,
|
"response_length": len(self.response) if self.response else None,
|
||||||
"execution_time_ms": self.execution_time_ms,
|
"execution_time_ms": self.execution_time_ms,
|
||||||
"error_type": self.error_type,
|
"error_type": self.error_type,
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class Telemetry:
|
||||||
_curr_user_id = None
|
_curr_user_id = None
|
||||||
|
|
||||||
def __init__(self):
|
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:
|
if telemetry_disabled:
|
||||||
self._posthog_client = None
|
self._posthog_client = None
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue