mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
security: harden telemetry, cloud defaults, and credential scoping
Changes: - Disable telemetry by default (opt-in instead of opt-out) - Strip user query text and LLM response text from telemetry events - Remove server_identifiers and tools_available_names from telemetry - Default search_scope to 'local' (no auto-import of unverified cloud skills) - Default auto_import to False in search_skills - Scope OpenClaw credential reading to openspace env block only - Enable sandbox by default in security config - Add SECURITY.md with guidance for users See PR description for full security audit findings.
This commit is contained in:
parent
1bd1a3d377
commit
0711cfd7fa
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"],
|
||||
"windows": ["del", "format", "rd", "rmdir", "/s", "/q", "taskkill", "/f"]
|
||||
},
|
||||
"sandbox_enabled": false
|
||||
"sandbox_enabled": true
|
||||
},
|
||||
"backend": {
|
||||
"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]:
|
||||
"""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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue