Back to skill

Security audit

Openclaw Memory

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but its remote LLM observer can expose conversation text and may send the wrong API key to an external provider.

Review before installing or using the Observer. Prefer passing an explicit provider-specific apiKey, avoid relying on environment fallback, and do not process secrets or private conversations unless you are comfortable sending that text to the chosen LLM provider. Treat extracted facts as untrusted until reviewed before adding them to persistent memory.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
src/observer.ts:84
Finding

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

Content
View full analysis
{ 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 — key in header, not URL (avoids log exposure) const res = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/${this.config.model || 'gemini-2.0-flash'}:generateContent`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-goog-api-key': key }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }), } ); const data = await res.json() as any; return data.candidates?.[0]?.content?.parts?.[0]?.text || ''; } ``` ### Technical Ana ...[truncated 2453 chars]
Remediation
View remediation

T02 · Agent Memory Poisoning

Warning
Location
src/observer.ts:72
Finding

Untrusted Conversation Content Can Manipulate Extracted Memory Facts Through Prompt Injection

Content
View full analysis
{ const sanitized = messages.map(m => `${m.role}: ${this.sanitize(m.content)}`).join('\n'); const prompt = EXTRACTION_PROMPT + sanitized; let response: string; try { response = await this.callLLM(prompt); } catch (err) { console.warn('[memory] LLM extraction failed:', (err as Error).message); return []; } return this.parseResponse(response, source); } ``` The model's output is then accepted based primarily on delimiter formatting: ```typescript private parseResponse(text: string, source: string): Observation[] { const kindMap: Record = { W: 'world', B: 'biographical', O: 'opinion', S: 'observation' }; const prioMap: Record = { H: 'high', M: 'medium', L: 'low' }; const obs: Observation[] = []; for (const line of text.split('\n')) { const parts = line.split('|').map(s => s.trim()); if (parts.length < 4) continue; const kind = kindMap[parts[0]]; const priority = prioMap[parts[1]]; if (!kind || !priority) continue; const entities = parts[2].split(',').map(e => e.trim().replace(/^@/, '')).filter(Boolean); const content = parts.slice(3).join('|'); const confMatch = content.match(/confidence:\s*([\d.]+)/i); obs.push({ kind, timestamp: new Date(), entities, content: content.replace(/\(confidence:.*?\)/i, '').trim(), source, priority, confidence: confMatch ? parseFloat(confMatch[1]) : undefined, }); } return obs; } ``` ### Technical Analysis The `sanitize()` method removes ...[truncated 2847 chars]
Remediation
View remediation
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 (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The code in src/alma.ts is consistent with the ALMA portion of the description: it operates offline, evolves candidate designs through mutation, records evaluations, and ranks best results using a local database. However, the declared description presents a broader skill whose purpose includes agent memory, LLM fact extraction, full-text search, and remote API usage by an Observer. None of those behaviors appear in this supplied chunk. Because the actual code’s primary behavior is significantly narrower than the declared capability set, the description does not accurately represent what this code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The supplied code does not implement memory, meta-learning, fact extraction, full-text search, or any remote LLM API interaction. Its primary purpose is security scanning of text for suspicious Unicode characters associated with hidden prompt injection, including categorization, severity scoring, and decoding embedded tag payloads. This is a materially different function from the declared description, so the description does not accurately represent the code.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill metadata declares no explicit tool scope even though the description clearly indicates access to environment variables and outbound network use via remote LLM APIs. Missing permission declarations can cause reviewers or runtime policy systems to underestimate the skill's capabilities, increasing the risk of unintended secret access or data egress.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The Observer is described as sending conversation history to third-party LLM APIs, but the documentation does not provide a clear, prominent privacy warning or guidance on consent, minimization, and sensitive-data handling. If users or agents pass secrets, personal data, or confidential workspace content, that information may be transmitted off-device unexpectedly.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The observer sends full conversation content to third-party LLM APIs, which can expose sensitive user data, secrets, or personal information outside the local environment. In a memory/extraction skill, this is especially relevant because users may not realize their historical conversations are being transmitted off-box for processing.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
78% confidence
Finding

The hardcoded OpenAI endpoint confirms an external recipient for conversation data, reinforcing the data egress risk already present at this call site. The danger is not the domain string itself, but that it represents transmission of user conversation content to a third-party processor.

Content

Scanner excerpt · src/observer.ts (reported line 88)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
78% confidence
Finding

The hardcoded OpenAI endpoint confirms an external recipient for conversation data, reinforcing the data egress risk already present at this call site. The danger is not the domain string itself, but that it represents transmission of user conversation content to a third-party processor.

Content

Scanner excerpt · src/observer.ts (reported line 88)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
78% confidence
Finding

The Anthropic endpoint string indicates remote transmission of potentially sensitive memory extraction input to a third party. In this skill context, where conversations are mined for structured facts, externalizing that text can leak personal or confidential information if users are not adequately informed or protected.

Content

Scanner excerpt · src/observer.ts (reported line 102)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
78% confidence
Finding

The Anthropic endpoint string indicates remote transmission of potentially sensitive memory extraction input to a third party. In this skill context, where conversations are mined for structured facts, externalizing that text can leak personal or confidential information if users are not adequately informed or protected.

Content

Scanner excerpt · src/observer.ts (reported line 102)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 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
"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 54)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
79% confidence
Finding

The manifest includes vitest as an unpinned devDependency, and vitest has multiple known advisories affecting some versions. While this does not prove the installed version is vulnerable, the lack of pinning means a vulnerable release could be resolved in developer or CI environments, potentially exposing file read or code execution issues during testing workflows.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code accesses provider API keys from process environment variables, which is a sensitive credential-handling operation under the warning criteria for code files. There is no nearby comment, disclosure, or user-visible notice explaining that the skill will read these credentials from the environment.

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:85