Back to skill

Security audit

Bounded Memory

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims, but it stores private conversation history locally and can send snippets plus possibly the wrong API credential to a hard-coded external LLM provider.

Review before installing. Use the offline search mode only if you are comfortable creating a local searchable copy of past conversations. Avoid --llm unless you specifically intend to send excerpts to MiniMax, and do not rely on a non-MiniMax or OpenAI key being kept separate by this script. Prefer ClawHub installation over the README manual clone command until the repository-owner mismatch is corrected.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/search-sessions.py:22
Finding
Private Conversation Excerpts and Potentially Mismatched API Credentials Sent to a Hard-Coded Provider## Vulnerability Details **File Location**: `scripts/search-sessions.py`, lines 22–40 and 108–133 **Vulnerability Type**: Provider-confusion flaw causing sensitive-data and credential disclosure **Risk Level**: High ### Vulnerable Code ```python def get_api_key(): """Find MiniMax/OpenAI API key from environment or OpenClaw config.""" # Try OpenClaw config try: cfg_path = os.path.expanduser("~/.openclaw/openclaw.json") with open(cfg_path) as f: config = json.load(f) # Check minimax provider in config providers = config.get("providers", {}) for p_name, p_cfg in providers.items(): for key_name in ("apiKey", "api_key"): if key_name in p_cfg: return p_cfg[key_name], p_name except Exception: pass # Env vars for var in ("MINIMAX_API_KEY", "OPENAI_API_KEY"): key = os.environ.get(var) if key: return key, var return None, None ``` ```python def summarize(query, results_text): """Summarize search results using MiniMax LLM.""" api_key, provider = get_api_key() if not api_key: return None, "⚠️ No API key found. Skipping summary." # MiniMax compatible OpenAI endpoint BASE_URL = "https://api.minimax.chat/v1" MODEL = "MiniMax-M2.7" prompt = f"""User asked: "{query}" Relevant conversation excerpts: --- {results_text} --- Briefly summarize whether these excerpts answer the user's question. Answer in Chinese, 1-2 sentences.""" payload = { "model": MODEL, "messages": [{"role": "user", "content": prompt}], "max_tokens": 300, "temperature": 0.3, } headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}", } req = urllib.request.Request( f"{BASE_URL}/chat/completions", data=json.dumps(payload).encode("utf-8"), headers=headers, method="POST", ) `` ...[truncated 2419 chars]
Remediation
## Remediation Suggestions 1. Bind each credential to its corresponding provider and endpoint. Never select the first arbitrary provider credential. 2. If MiniMax is the only supported service, accept only `MINIMAX_API_KEY` or an explicitly named MiniMax configuration entry. 3. If multiple providers are supported, derive the endpoint, model, and credential from one validated provider configuration object. 4. Reject unsupported providers and endpoint/credential mismatches instead of silently falling back. 5. Before transmission, display the destination hostname, provider, model, and categories of data being sent. 6. Require explicit confirmation before transmitting conversation excerpts, particularly on the first use of each provider. 7. Minimize the transmitted data by stripping session identifiers, tool commands, credentials, and unrelated text. 8. Add secret-detection and redaction before constructing the request. 9. Update the documentation to accurately state the endpoint used and the precise disclosure behavior. 10. Avoid returning remote HTTP response bodies directly to the terminal because they may expose unnecessary provider diagnostics.

T08 · Insecure Dependencies

Error
Location
README.md:3
Finding
Manual Installation Instructions Reference an Inconsistent Repository Owner## Vulnerability Details **File Location**: `README.md`, lines 3, 46, and 114–116 **Vulnerability Type**: Repository substitution and typo-squatting risk **Risk Level**: High ### Vulnerable Documentation ```markdown [![Version](https://img.shields.io/badge/version-v1.1.1-blue.svg)](https://github.com/canmaxice-maker/bounded-memory) ``` ```markdown # Manual git clone https://github.com/canmaxfire/bounded-memory.git mv bounded-memory ~/.openclaw/workspace/main/skills/session-search ``` ```markdown | [v1.1.1](https://github.com/canmaxice-maker/bounded-memory/releases/tag/v1.1.1) | 2026-04-21 | Fix: LLM is now truly opt-in, docs match code | | [v1.1.0](https://github.com/canmaxice-maker/bounded-memory/releases/tag/v1.1.0) | 2026-04-21 | Rewrite: plain language, user benefit focus | | [v1.0.0](https://github.com/canmaxice-maker/bounded-memory/releases/tag/v1.0.0) | 2026-04-21 | Initial release | ``` ### Technical Analysis The badge and release links identify the repository owner as `canmaxice-maker`, while the manual installation command clones from `canmaxfire`. These are materially different repository identities. Users following the manual installation procedure may therefore obtain executable Skill content from a repository other than the one represented by the project's badges and release history. Because the instructions then move that content into OpenClaw's trusted skills directory, substituted scripts or Skill instructions could later be invoked with the user's permissions. The audited files do not establish which owner is authoritative. The confirmed issue is the inconsistency itself and the resulting supply-chain exposure. ### Attack Path 1. A user chooses the documented manual installation method. 2. The user executes: ```bash git clone https://github.com/canmaxfire/bounded-memory.git ``` 3. Git retrieves content from the repository named in that command rather than the repository referenced by the badges and releases. 4. The user move ...[truncated 860 chars]
Remediation
## Remediation Suggestions 1. Determine the authoritative repository owner and use that exact identity throughout the README, badges, release links, and installation commands. 2. Remove or correct the inconsistent manual-clone URL immediately. 3. Pin manual installations to a reviewed commit hash or signed release tag rather than an unpinned branch. 4. Publish cryptographic checksums or signed release artifacts. 5. Recommend verification of the repository owner, commit identifier, and signature before moving files into the trusted skills directory. 6. Add automated documentation tests that compare installation URLs with release and project URLs. 7. If the inconsistent repository is not controlled by the project, notify users and rotate or revoke any affected distribution references.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/index-sessions.py:84
Finding
Full Conversation History Is Stored in a Plaintext Database Without Enforced Restrictive Permissions## Vulnerability Details **File Location**: `scripts/index-sessions.py`, lines 84–99 and 137–145 **Vulnerability Type**: Insecure storage of sensitive conversation data **Risk Level**: Medium ### Vulnerable Code ```python def init_db(): """Create SQLite FTS5 tables.""" os.makedirs(DB_DIR, exist_ok=True) conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute(""" CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts USING fts5( session_id, message_id, role, text, timestamp, tokenize='unicode61 remove_diacritics 2' ) """) c.execute(""" CREATE TABLE IF NOT EXISTS index_meta ( session_file TEXT PRIMARY KEY, last_modified TEXT, entries_indexed INTEGER DEFAULT 0 ) """) conn.commit() return conn ``` ```python entries = [ (session_id, m["message_id"], m["role"], m["text"], m["timestamp"]) for m in messages ] conn.executemany( "INSERT OR REPLACE INTO sessions_fts VALUES (?, ?, ?, ?, ?)", entries ) conn.execute( "INSERT OR REPLACE INTO index_meta VALUES (?, ?, ?)", (sf_abs, mtime, len(messages)) ) ``` ### Technical Analysis The indexer creates a SQLite database and copies complete extracted user and assistant messages into an FTS5 table. Extracted content can also include truncated tool-command arguments. The database is unencrypted and is specifically designed to make the entire retained history searchable. `os.makedirs(DB_DIR, exist_ok=True)` and `sqlite3.connect(DB_PATH)` do not explicitly enforce owner-only permissions. Access therefore depends on pre-existing directory permissions and the process umask. Under a permissive environment, other local users or processes may be able to read the database. The indexed copy increases exposure because it consolidates sensitive information from many session files into one predictable location. It also remains present after the original sessions are changed or removed u ...[truncated 1300 chars]
Remediation
## Remediation Suggestions 1. Create the database directory with owner-only permissions: ```python os.makedirs(DB_DIR, mode=0o700, exist_ok=True) os.chmod(DB_DIR, 0o700) ``` 2. Create or immediately change the database file to mode `0600` and verify its owner. 3. Reject symbolic links and unexpected pre-existing database paths before opening the database. 4. Document clearly that a plaintext, searchable copy of conversation history is retained. 5. Provide commands for secure deletion, retention limits, selective indexing, and complete rebuilding. 6. Remove stale records when source sessions are deleted or when agents are no longer selected. 7. Redact likely credentials and sensitive tool arguments before indexing. 8. Consider operating-system key-store integration or encryption at rest where local threat models require it. 9. Add automated tests verifying restrictive permissions under permissive umask settings.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (4)

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This script indexes full user and assistant session content, including snippets from tool-call arguments, into a persistent local SQLite FTS database without any notice, consent flow, minimization, or access-control safeguards. Because session transcripts can contain secrets, personal data, or command contents, this creates a real confidentiality risk if the host is shared, backed up, exfiltrated, or later searched by other local tooling.

External Transmission

Medium
Category
Data Exfiltration
Content
return None, "⚠️  No API key found. Skipping summary."

    # MiniMax compatible OpenAI endpoint
    BASE_URL = "https://api.minimax.chat/v1"
    MODEL = "MiniMax-M2.7"

    prompt = f"""User asked: "{query}"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
When --llm is enabled, the script passes formatted session search results into summarize(), which transmits conversation excerpts to an external API. Although the module docstring notes that LLM summarization is opt-in, the runtime path does not provide an explicit user-facing warning that local session content will be sent off-device.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The prompt hardcodes 'Answer in Chinese' regardless of user preference or locale. This is a natural-language policy issue because it forces a specific language without offering a choice or documenting a justified locale constraint.

Static analysis

No suspicious patterns detected.