Back to skill

Security audit

Openclaw Memories

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated memory purpose, but a real credential-handling flaw can send an API key to the wrong LLM provider.

Review before installing or using in a sensitive environment. Use explicit provider-specific apiKey values, avoid relying on environment-variable fallback, and do not pass private conversation history to Observer unless the selected LLM provider is approved for that data. ALMA and Indexer appear suitable for offline use.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
src/observer.ts:67
Finding

Provider-Agnostic API-Key Fallback Can Disclose Credentials to the Wrong LLM Provider

Content
View full analysis

Vulnerability Details

File Location: src/observer.ts, lines 67–108
Vulnerability Type: Cross-provider credential disclosure caused by insecure credential selection
Risk Level: High

Vulnerable Code

ts
private async callLLM(prompt: string): Promise<string> {
  const key = this.config.apiKey || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || '';

  if (this.config.provider === 'openai') {
    const res = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
      body: JSON.stringify({
        model: this.config.model || 'gpt-4o-mini',
        messages: [{ role: 'user', content: prompt }],
        max_tokens: 2000,
      }),
    });
    const data = await res.json() as any;
    return data.choices?.[0]?.message?.content || '';
  }

  if (this.config.provider === 'anthropic') {
    const res = await fetch('https://api.anthropic.com/v1/messages', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': key,
        'anthropic-version': '2023-06-01',
      },
      body: JSON.stringify({
        model: this.config.model || 'claude-3-5-haiku-20241022',
        max_tokens: 2000,
        messages: [{ role: 'user', content: prompt }],
      }),
    });
    const data = await res.json() as any;
    return data.content?.[0]?.text || '';
  }

  // Gemini
  const res = await fetch(
    `https://generativelanguage.googleapis.com/v1beta/models/${this.config.model || 'gemini-2.0-flash'}:generateContent?key=${key}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
    }
  );
  const data = await res.json() as any;
  return data.candidates?.[0]?.content?.parts?.[0]?.text || '';
}

Technical Analysis

The credential-selection logic is independent of the confi ...[truncated 3177 chars]

Remediation
View remediation

Remediation Suggestions

  1. Select credentials strictly according to the configured provider:

    ts
    private getApiKey(): string {
      if (this.config.apiKey) return this.config.apiKey;
    
      switch (this.config.provider) {
        case 'openai':
          return process.env.OPENAI_API_KEY || '';
        case 'anthropic':
          return process.env.ANTHROPIC_API_KEY || '';
        case 'gemini':
          return process.env.GEMINI_API_KEY || '';
      }
    }
    
  2. Reject missing credentials before constructing or sending a request:

    ts
    const key = this.getApiKey();
    if (!key) {
      throw new Error(`Missing API key for provider: ${this.config.provider}`);
    }
    
  3. Never fall back to another provider's environment variable.

  4. Use an authentication header for Gemini if supported by the selected API interface. If the API requires a query parameter, ensure request URLs are redacted from logs, telemetry, and exception messages.

  5. Validate the provider and model combination so that unsupported or unexpected configuration fails before any sensitive data is transmitted.

  6. Add mocked unit tests that verify:

    • OpenAI receives only an explicit key or OPENAI_API_KEY.
    • Anthropic receives only an explicit key or ANTHROPIC_API_KEY.
    • Gemini receives only an explicit key or GEMINI_API_KEY.
    • Missing provider-specific credentials cause a local failure without a network request.
    • Test runs never contact real provider endpoints.
  7. Document GEMINI_API_KEY alongside the other supported environment variables and explain that conversation content is sent to the selected provider.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The code accurately matches the 'ALMA meta-learning' portion of the description: it mutates parameter sets, evaluates them, persists results locally, and tracks best-performing designs. However, the declared purpose describes a larger skill that also includes LLM fact extraction, full-text search, and an observer that calls remote model APIs. None of those behaviors appear in this chunk. Because the actual code is a narrower offline optimization/database module rather than the broader described memory system, the description does not accurately represent what this supplied code chunk actually does.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill metadata advertises capabilities that involve environment-variable access and remote network use, but it does not declare any explicit tool scope or permissions boundaries. In an agent ecosystem, this weakens reviewability and can cause the skill to be granted broader access than users expect, increasing the chance of secret exposure or unintended outbound requests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code concatenates full conversation messages into a prompt and sends them to third-party LLM providers without any in-code consent gate, redaction step, or user-facing disclosure. Because the extracted target data explicitly includes biographical facts and opinions, sensitive user-provided content can be transmitted off-device/off-service in a way users may not expect.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

extract() forwards the entire conversation text to an external LLM to derive memory facts, including biographical and opinion content. This creates a direct natural-language exfiltration path for sensitive information embedded anywhere in the chat, even if that information was never intended to be stored or shared externally.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This network call transmits prompt contents to the OpenAI API, and the prompt is built from full conversation messages. In the context of a memory skill, that means potentially sensitive personal or confidential chat data is sent to an external service, making privacy leakage the main risk.

Content

Scanner excerpt · src/observer.ts (reported line 71)May include surrounding context.

ts
const key = this.config.apiKey || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || '';

    if (this.config.provider === 'openai') {
      const res = await fetch('https://api.openai.com/v1/chat/completions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
        body: JSON.stringify({

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This network call transmits prompt contents to the OpenAI API, and the prompt is built from full conversation messages. In the context of a memory skill, that means potentially sensitive personal or confidential chat data is sent to an external service, making privacy leakage the main risk.

Content

Scanner excerpt · src/observer.ts (reported line 71)May include surrounding context.

ts
const key = this.config.apiKey || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || '';

    if (this.config.provider === 'openai') {
      const res = await fetch('https://api.openai.com/v1/chat/completions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${key}` },
        body: JSON.stringify({

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This request sends the assembled conversation prompt to Anthropic's API, again creating an external transmission path for all included chat content. The danger is increased by the skill's purpose of extracting memory from personal conversations, which naturally encourages processing of private user details.

Content

Scanner excerpt · src/observer.ts (reported line 85)May include surrounding context.

ts
}

    if (this.config.provider === 'anthropic') {
      const res = await fetch('https://api.anthropic.com/v1/messages', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',

External Transmission

Medium
Category
Data Exfiltration
Confidence
93% confidence
Finding

This request sends the assembled conversation prompt to Anthropic's API, again creating an external transmission path for all included chat content. The danger is increased by the skill's purpose of extracting memory from personal conversations, which naturally encourages processing of private user details.

Content

Scanner excerpt · src/observer.ts (reported line 85)May include surrounding context.

ts
}

    if (this.config.provider === 'anthropic') {
      const res = await fetch('https://api.anthropic.com/v1/messages', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 51)May include surrounding context.

json
],
  "dependencies": {},
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0",
    "vitest": "^1.0.0"
  }

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 52)May include surrounding context.

json
"dependencies": {},
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0",
    "vitest": "^1.0.0"
  }
}

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 53)May include surrounding context.

json
"devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0",
    "vitest": "^1.0.0"
  }
}

Unverifiable Dependency: vitest has 3 known advisory(ies) (CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock); CVE-2025-24964 (Vitest allows Remote Code Execution when accessing a malicious website while Vit)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
81% confidence
Finding

The manifest references Vitest with an unpinned range while known advisories exist for some Vitest releases, so the installed version could be vulnerable depending on resolution time. Although Vitest is a devDependency rather than a runtime dependency, vulnerable test tooling can still expose developers or CI systems to file read or code execution risks during local testing or automated pipelines.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The cleanup hook removes testDir with rmSync(..., { recursive: true, force: true }), which is a destructive filesystem operation. In this file there is no confirmation, log, or comment disclosing that the test will delete files under the generated temporary directory.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/observer.ts:68