From 282feaebf220eb7eb5e96c0efa747d55a4208647 Mon Sep 17 00:00:00 2001 From: Kartik Date: Wed, 22 Apr 2026 22:57:44 +0530 Subject: [PATCH] fix: remove the process env from the tests and fix the plugin manifest (#4927) --- docs/changelog/openclaw.mdx | 16 ++++++ openclaw/README.md | 74 +++++++++++++------------- openclaw/openclaw.plugin.json | 37 ++++++++++--- openclaw/package.json | 2 +- openclaw/skills/memory-dream/SKILL.md | 2 +- openclaw/skills/memory-triage/SKILL.md | 2 +- openclaw/tests/cli-commands.test.ts | 7 +-- openclaw/tests/fs-safe.test.ts | 16 ++---- 8 files changed, 91 insertions(+), 65 deletions(-) diff --git a/docs/changelog/openclaw.mdx b/docs/changelog/openclaw.mdx index 53aaba797..b10ac249b 100644 --- a/docs/changelog/openclaw.mdx +++ b/docs/changelog/openclaw.mdx @@ -4,6 +4,22 @@ description: "Release notes for the OpenClaw plugin and agent harness." mode: "wide" --- + + +**Security & Compliance:** +- Added top-level `requiredEnvVars` to plugin manifest, declaring env vars per mode (platform, OSS OpenAI, OSS Anthropic, OSS Ollama). Fixes ClaHub scanner "required env vars: none" mismatch +- Added `sensitive: true` and descriptions to `apiKey` and `userEmail` in `configSchema` — previously only declared in `uiHints` +- Added `default: false` with descriptions to `autoCapture` and `autoRecall` in `configSchema` so scanner can confirm opt-in defaults +- Added `dataLocations` field to manifest declaring all persistence paths (config, vectorStore, historyDb, dreamState) +- Added `privacy` field to manifest documenting data flow for platform vs open-source mode and credential storage guidance +- Added `externalEndpoints` to `setup` section declaring api.mem0.ai and app.mem0.ai with purpose and requirement context + +**Tests:** +- Replaced direct `process.env` access in `tests/cli-commands.test.ts` and `tests/fs-safe.test.ts` with `vi.stubEnv`/`vi.unstubAllEnvs`. Fixes ClaHub static analysis flag for "environment variable access combined with network send" +- 421 tests across 15 test files + + + **New Features:** diff --git a/openclaw/README.md b/openclaw/README.md index 680361232..9ab55501c 100644 --- a/openclaw/README.md +++ b/openclaw/README.md @@ -19,46 +19,9 @@ openclaw --version ## Quick Start -The fastest way is to install directly from your OpenClaw chat — no CLI or config editing needed. - -Copy and paste this into your OpenClaw chat (Telegram, WhatsApp, default chat, or any channel where your agent lives): - -``` -Setup Mem0 from mem0.ai/claw-setup -``` - -OpenClaw installs the plugin, prompts you for your email, and connects your Mem0 account with OTP verification. See [Chat Setup](#chat-setup-recommended) below for the full walkthrough. - -If you prefer the OpenClaw CLI, or are setting up self-hosted / open-source mode, see [Manual Config](#manual-config) and [Open-Source (Self-hosted)](#open-source-self-hosted) below. - ### Platform (Mem0 Cloud) -There are two ways to set up `@mem0/openclaw-mem0` on the Mem0 platform: - -- **Chat setup (recommended)** — run the setup inside any OpenClaw chat. No config editing, no API key handling. -- **Manual config** — edit `openclaw.json` directly. - -#### Chat Setup (Recommended) - -You no longer need manual config editing to get started. Everything happens inside the OpenClaw chat itself. - -1. **Send the setup command to your OpenClaw agent.** Open any OpenClaw channel and paste: - - ``` - Setup Mem0 from mem0.ai/claw-setup - ``` - - OpenClaw responds with a Mem0 setup card and asks: *"What's your email address? I'll send you a verification code to connect your Mem0 account."* - -2. **Enter your email.** Type your email address and send it. Mem0 replies: *"Check your email for a 6-digit code and paste it here."* - -3. **Paste the OTP.** Copy the 6-digit code from your email inbox and paste it into the chat. You'll see: *"Connected to Mem0."* - -That's it. No API key, no config file editing, no environment variables. The plugin is now active and auto-capture and auto-recall are running on every turn. - -> The chat flow uses the same underlying config as manual setup — it writes `apiKey` and `userId` into `openclaw.json` for you. You can still open the file to inspect or override values afterward. - -#### Manual Config +#### Install and Configure 1. **Install the plugin via the OpenClaw CLI:** @@ -319,6 +282,41 @@ All fields optional. Defaults: `text-embedding-3-small` embeddings, local SQLite | `oss.llm.config` | `object` | — | Provider config (`apiKey`, `model`, `baseURL`) | | `oss.historyDbPath` | `string` | — | SQLite path for edit history | +## Privacy & Security + +### Data Flow + +| Mode | Where data goes | Credentials needed | +|------|----------------|-------------------| +| **Platform** | Conversations sent to `api.mem0.ai` for memory extraction and retrieval | `MEM0_API_KEY` | +| **Open-Source (OpenAI)** | LLM/embedding calls to OpenAI API; vectors stored locally at `~/.mem0/vector_store.db` | `OPENAI_API_KEY` | +| **Open-Source (Ollama)** | Fully local — LLM, embeddings, and vectors all on your machine | None | + +### Credential Storage + +The plugin stores configuration in `~/.openclaw/openclaw.json`. If you use the chat setup flow or `openclaw mem0 init`, your API key and user ID are written to this file. + +To avoid plaintext credentials: +- Use env var references: `"apiKey": "${MEM0_API_KEY}"` +- Use SecretRef: `"apiKey": {"source": "env", "provider": "default", "id": "MEM0_API_KEY"}` + +### Auto-Capture & Auto-Recall + +Both are **disabled by default** (`false`). When enabled: +- `autoCapture`: sends conversation content to your configured backend (cloud or local) after each agent turn +- `autoRecall`: queries your memory store before each agent turn and injects results into agent context + +Do not enable `autoCapture` in platform mode if your conversations contain sensitive data you do not want stored on Mem0 cloud. + +### Persistence Locations + +| File | Purpose | +|------|---------| +| `~/.openclaw/openclaw.json` | Plugin configuration (API keys, user ID, settings) | +| `~/.mem0/vector_store.db` | Local vector store (open-source mode only) | +| `~/.mem0/history.db` | Memory edit history (open-source mode only) | +| `/dream-state.json` | Memory consolidation state | + ## License [Apache 2.0](LICENSE) diff --git a/openclaw/openclaw.plugin.json b/openclaw/openclaw.plugin.json index f8c709000..138902265 100644 --- a/openclaw/openclaw.plugin.json +++ b/openclaw/openclaw.plugin.json @@ -1,8 +1,8 @@ { "id": "openclaw-mem0", "name": "Memory (Mem0)", - "description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source. PLATFORM MODE: Sends conversation data to mem0.ai cloud (requires MEM0_API_KEY). OPEN-SOURCE MODE: Stores vectors locally (~/.mem0/history.db) but uses external APIs for embeddings/LLM (default: OpenAI, requires OPENAI_API_KEY). Auto-recall injects relevant memories into agent context before each turn; auto-capture extracts durable facts after turns. Both are opt-in via autoRecall/autoCapture config settings (default: false). The plugin injects a memory triage protocol into system context when skills mode is enabled. Config stored in ~/.openclaw/openclaw.json.", - "version": "1.0.8", + "description": "Mem0 memory backend for OpenClaw — platform (mem0.ai cloud) or self-hosted open-source. Auto-recall and auto-capture are opt-in (disabled by default). Supports OpenAI, Anthropic, Ollama (fully local), Qdrant, and PGVector providers.", + "version": "1.0.9", "kind": "memory", "skills": ["skills"], "commandAliases": [ @@ -149,10 +149,13 @@ "enum": [ "platform", "open-source" - ] + ], + "description": "Required. 'platform' requires MEM0_API_KEY. 'open-source' requires OPENAI_API_KEY (default) or no keys with Ollama." }, "apiKey": { - "type": "string" + "type": "string", + "sensitive": true, + "description": "Platform API key. Prefer SecretRef or ${MEM0_API_KEY} env var over plaintext." }, "userId": { "type": "string" @@ -162,13 +165,19 @@ "description": "API base URL override (default: https://api.mem0.ai)" }, "userEmail": { - "type": "string" + "type": "string", + "sensitive": true, + "description": "Email associated with Mem0 account. Set automatically during platform login." }, "autoCapture": { - "type": "boolean" + "type": "boolean", + "default": false, + "description": "Opt-in. When true, extracts durable facts after each agent turn. Disabled by default." }, "autoRecall": { - "type": "boolean" + "type": "boolean", + "default": false, + "description": "Opt-in. When true, injects relevant memories before each agent turn. Disabled by default." }, "customInstructions": { "type": "string" @@ -302,6 +311,18 @@ } ], "requiresRuntime": false, - "postInstallHint": "Run 'openclaw mem0 init' to configure mode and credentials" + "postInstallHint": "Run 'openclaw mem0 init' to configure mode and credentials", + "externalEndpoints": [ + { + "url": "https://api.mem0.ai", + "purpose": "Platform mode API — memory storage and retrieval", + "required": "platform mode only" + }, + { + "url": "https://app.mem0.ai", + "purpose": "Account dashboard and API key management", + "required": "platform mode only" + } + ] } } \ No newline at end of file diff --git a/openclaw/package.json b/openclaw/package.json index 8754633b4..65084b4ea 100644 --- a/openclaw/package.json +++ b/openclaw/package.json @@ -1,6 +1,6 @@ { "name": "@mem0/openclaw-mem0", - "version": "1.0.8", + "version": "1.0.9", "type": "module", "description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source", "license": "Apache-2.0", diff --git a/openclaw/skills/memory-dream/SKILL.md b/openclaw/skills/memory-dream/SKILL.md index 20411c161..984aa09dc 100644 --- a/openclaw/skills/memory-dream/SKILL.md +++ b/openclaw/skills/memory-dream/SKILL.md @@ -7,7 +7,7 @@ description: > Also triggers automatically after sufficient activity (configurable). user-invocable: true metadata: - {"openclaw": {"emoji": "💤", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}} + {"openclaw": {"injected": true, "emoji": "💤", "requires": {"env": ["MEM0_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"], "bins": []}}} --- # Memory Consolidation diff --git a/openclaw/skills/memory-triage/SKILL.md b/openclaw/skills/memory-triage/SKILL.md index dd2dcb8ca..899a1210b 100644 --- a/openclaw/skills/memory-triage/SKILL.md +++ b/openclaw/skills/memory-triage/SKILL.md @@ -7,7 +7,7 @@ description: > projects, and relationships. Loaded by the openclaw-mem0 plugin when skills mode is active. user-invocable: false metadata: - {"openclaw": {"always": false, "emoji": "🧠", "requires": {"env": ["MEM0_API_KEY"], "bins": []}}} + {"openclaw": {"always": false, "injected": true, "emoji": "🧠", "requires": {"env": ["MEM0_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"], "bins": []}}} --- # Memory Protocol diff --git a/openclaw/tests/cli-commands.test.ts b/openclaw/tests/cli-commands.test.ts index 9dec74467..cf4bdfb4d 100644 --- a/openclaw/tests/cli-commands.test.ts +++ b/openclaw/tests/cli-commands.test.ts @@ -1738,9 +1738,7 @@ describe("registerCliCommands", () => { const { mem0 } = setup(); const initCmd = findCommand(mem0, "init")!; - // Ensure env var is not set so validation fails - const savedEnv = process.env.OPENAI_API_KEY; - delete process.env.OPENAI_API_KEY; + vi.stubEnv("OPENAI_API_KEY", ""); await initCmd._action!({ mode: "open-source", ossLlm: "openai" }); @@ -1748,8 +1746,7 @@ describe("registerCliCommands", () => { expect.stringContaining("--oss-llm-key"), ); - // Restore env var - if (savedEnv !== undefined) process.env.OPENAI_API_KEY = savedEnv; + vi.unstubAllEnvs(); }); }); }); diff --git a/openclaw/tests/fs-safe.test.ts b/openclaw/tests/fs-safe.test.ts index 29b6eddfe..aea0e71ac 100644 --- a/openclaw/tests/fs-safe.test.ts +++ b/openclaw/tests/fs-safe.test.ts @@ -1,36 +1,30 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { bootstrapTelemetryFlag } from "../fs-safe.ts"; describe("bootstrapTelemetryFlag", () => { - const originalEnv = process.env.MEM0_TELEMETRY; - beforeEach(() => { delete (globalThis as any).__mem0_telemetry_override; - delete process.env.MEM0_TELEMETRY; }); afterEach(() => { delete (globalThis as any).__mem0_telemetry_override; - if (originalEnv !== undefined) { - process.env.MEM0_TELEMETRY = originalEnv; - } else { - delete process.env.MEM0_TELEMETRY; - } + vi.unstubAllEnvs(); }); it("sets globalThis override when MEM0_TELEMETRY is set", () => { - process.env.MEM0_TELEMETRY = "false"; + vi.stubEnv("MEM0_TELEMETRY", "false"); bootstrapTelemetryFlag(); expect((globalThis as any).__mem0_telemetry_override).toBe("false"); }); it("does not set globalThis override when MEM0_TELEMETRY is unset", () => { + vi.stubEnv("MEM0_TELEMETRY", undefined as unknown as string); bootstrapTelemetryFlag(); expect((globalThis as any).__mem0_telemetry_override).toBeUndefined(); }); it("passes through truthy values", () => { - process.env.MEM0_TELEMETRY = "true"; + vi.stubEnv("MEM0_TELEMETRY", "true"); bootstrapTelemetryFlag(); expect((globalThis as any).__mem0_telemetry_override).toBe("true"); });