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
12 changes: 8 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ NEURAL_TOKEN=your-secret-token-here
NEURAL_ENGINE_ACCESS_TOKEN=your-secret-token-here
NEURAL_TOKEN_OLD=your-previous-token-here
NEURAL_TOKEN_NEW=your-pending-token-here

# Required when running docker compose Grafana service
GRAFANA_ADMIN_PASSWORD=your-strong-grafana-password
ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
# Optional: Set to true if deployed behind a trusted reverse proxy (Cloudflare/Nginx) to respect CF-Connecting-IP & X-Forwarded-For headers
# TRUST_PROXY_HEADERS=false
Expand Down Expand Up @@ -79,8 +82,9 @@ BUNKER_NODES=bunker-1:Swarm Bunker 1:tadpole-linux,bunker-2:Swarm Bunker 2:tadpo
PRIVACY_MODE=true

# Sandbox Execution Boundaries (Default-deny bare host execution)
# To execute skills or shell commands, enable at least one sandbox backend:
USE_SANDBOX_DOCKER=false
# Skill execution sandboxing (default-deny bare host).
# Recommended: Docker micro-containers. Keep host fallback false.
USE_SANDBOX_DOCKER=true
USE_SANDBOX_WASM=false
# Host fallback: Only set to true if intentionally executing uncontained host processes
ALLOW_HOST_SKILL_EXECUTION=false
Expand Down Expand Up @@ -140,8 +144,8 @@ SME_SYNC_INTERVAL_MINS=30
# -----------------------------------------------------------------------
# Security Sandboxing
# -----------------------------------------------------------------------
# USE_SANDBOX_DOCKER — Enable Docker container isolated skill execution
# USE_SANDBOX_DOCKER=false
# USE_SANDBOX_DOCKER — Enable Docker container isolated skill execution (recommended: true)
# USE_SANDBOX_DOCKER=true
# USE_SANDBOX_WASM — Enable WebAssembly (wasmtime) isolated skill execution
# USE_SANDBOX_WASM=false
# ALLOW_HOST_SKILL_EXECUTION — Allow unsandboxed skill execution on bare host (default: false)
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ audit_production_report.md
sidecar_panic.log
server-rs/errors.txt
server-rs/logs.txt

# Local Prometheus scrape credentials (copy from bearer_token.example)
monitoring/prometheus/bearer_token
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ services:
image: grafana/grafana:latest
container_name: grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD must be set in .env}
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- ./monitoring/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources
Expand Down
46 changes: 44 additions & 2 deletions execution/tadpole_mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,49 @@ def validate_arguments(args: dict, schema: dict):



# Environment keys allowed into legacy skill subprocesses.
# Explicitly excludes provider API keys and NEURAL_TOKEN*.
_SKILL_ENV_ALLOWLIST = frozenset({
"PATH",
"HOME",
"USER",
"USERNAME",
"LANG",
"LC_ALL",
"LC_CTYPE",
"TZ",
"TMPDIR",
"TEMP",
"TMP",
"PYTHONPATH",
"PYTHONHOME",
"VIRTUAL_ENV",
"SYSTEMROOT",
"COMSPEC",
"PATHEXT",
"WORKSPACE_ROOT",
"TADPOLE_SKILL_ARGS",
})


def build_skill_subprocess_env(arguments_json: str | None = None, source_env: dict | None = None) -> dict[str, str]:
"""Build a minimal env for skill subprocesses (no provider secrets)."""
src = source_env if source_env is not None else os.environ
env: dict[str, str] = {}
for key in _SKILL_ENV_ALLOWLIST:
if key == "TADPOLE_SKILL_ARGS":
continue
val = src.get(key)
if val is not None:
env[key] = val
if arguments_json is not None:
env["TADPOLE_SKILL_ARGS"] = arguments_json
elif "TADPOLE_SKILL_ARGS" in src:
env["TADPOLE_SKILL_ARGS"] = src["TADPOLE_SKILL_ARGS"]
return env



def load_skills():
"""Scans the execution directory for JSON manifests and loads them."""
global _TOOLS_CACHE, _TOOL_MANIFESTS
Expand Down Expand Up @@ -207,8 +250,7 @@ async def handle_call_tool(
return [_format_text_response(f"Argument Validation Failed: {str(err)}")]

args_json = json.dumps(arguments or {})
env = os.environ.copy()
env["TADPOLE_SKILL_ARGS"] = args_json
env = build_skill_subprocess_env(arguments_json=args_json)

workspace_root = os.environ.get("WORKSPACE_ROOT", os.getcwd())

Expand Down
2 changes: 0 additions & 2 deletions monitoring/prometheus/bearer_token

This file was deleted.

5 changes: 3 additions & 2 deletions monitoring/prometheus/bearer_token.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Paste your NEURAL_TOKEN here to enable Prometheus to scrape /metrics
test-token
# Copy this file to bearer_token and paste your NEURAL_TOKEN (one line, no quotes).
# Never commit bearer_token — it is gitignored.
your-neural-token-here
41 changes: 41 additions & 0 deletions tests/unit/test_mcp_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,47 @@ def dummy_call():
self.assertEqual(dummy_list(), [])
self.assertEqual(dummy_call(), [])


def test_build_skill_subprocess_env_excludes_secrets(self):
source = {
"PATH": "/usr/bin",
"HOME": "/home/tadpole",
"WORKSPACE_ROOT": "/workspace",
"OPENAI_API_KEY": "sk-secret",
"ANTHROPIC_API_KEY": "sk-ant-secret",
"GOOGLE_API_KEY": "goog-secret",
"GROQ_API_KEY": "groq-secret",
"DEEPSEEK_API_KEY": "ds-secret",
"REPLICATE_API_KEY": "r8-secret",
"NEURAL_TOKEN": "neural-secret",
"NEURAL_TOKEN_OLD": "old-secret",
"NEURAL_TOKEN_NEW": "new-secret",
"NEURAL_ENGINE_ACCESS_TOKEN": "engine-secret",
"UNRELATED_CUSTOM": "should-not-pass",
}
env = tadpole_mcp_server.build_skill_subprocess_env(
arguments_json='{"x":1}',
source_env=source,
)
self.assertEqual(env["PATH"], "/usr/bin")
self.assertEqual(env["HOME"], "/home/tadpole")
self.assertEqual(env["WORKSPACE_ROOT"], "/workspace")
self.assertEqual(env["TADPOLE_SKILL_ARGS"], '{"x":1}')
for secret_key in (
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GOOGLE_API_KEY",
"GROQ_API_KEY",
"DEEPSEEK_API_KEY",
"REPLICATE_API_KEY",
"NEURAL_TOKEN",
"NEURAL_TOKEN_OLD",
"NEURAL_TOKEN_NEW",
"NEURAL_ENGINE_ACCESS_TOKEN",
"UNRELATED_CUSTOM",
):
self.assertNotIn(secret_key, env)

if __name__ == "__main__":
unittest.main()

Expand Down
Loading