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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/search-sessions.py (reported line 113)May include surrounding context.

python
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}"

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.