Back to skill

Security audit

agent-memory

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed local memory library for agents, but users should treat its database as persistent plaintext and not rely on its delete operations for complete erasure.

Install only if you want an agent to keep local cross-session memory. Avoid storing secrets, credentials, regulated personal data, or third-party personal details unless you have a clear reason and consent. Review or back up the SQLite database before cleanup, and do not assume forget() fully removes fact text from the database until the FTS deletion issue is fixed.

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

Warning
Location
src/memory.py:351
Finding

Deleted Facts Remain in the Full-Text Search Index

Content
View full analysis

Vulnerability Details

File Location: src/memory.py:160-162, src/memory.py:215-217, and src/memory.py:351-373
Vulnerability Type: Incomplete deletion of plaintext memory records
Risk Level: Medium

Vulnerable Code

python
# Full-text search index for facts
cursor.execute("""
    CREATE VIRTUAL TABLE IF NOT EXISTS facts_fts 
    USING fts5(content, tags, tokenize='porter')
""")
python
# Add to FTS index
cursor.execute("""
    INSERT INTO facts_fts (rowid, content, tags)
    SELECT rowid, content, tags FROM facts WHERE id = ?
""", (fact_id,))
python
def forget(self, fact_id: str):
    """Permanently delete a fact."""
    conn = sqlite3.connect(self.db_path)
    cursor = conn.cursor()
    cursor.execute("DELETE FROM facts WHERE id = ?", (fact_id,))
    conn.commit()
    conn.close()

def forget_stale(self, days: int = 30, min_access_count: int = 1):
    """
    Remove facts that haven't been accessed in N days
    and have low access counts.
    """
    cutoff = (datetime.utcnow() - timedelta(days=days)).isoformat()

    conn = sqlite3.connect(self.db_path)
    cursor = conn.cursor()
    cursor.execute("""
        DELETE FROM facts
        WHERE last_accessed < ?
        AND access_count <= ?
        AND superseded_by IS NULL
    """, (cutoff, min_access_count))

    deleted = cursor.rowcount
    conn.commit()
    conn.close()

    return deleted

Technical Analysis

Each fact's content and tags are stored twice: once in the facts table and again in the standalone facts_fts FTS5 virtual table. The FTS table is not configured as an external-content table, and the schema does not install synchronization triggers.

Both deletion methods remove records only from facts. They do not delete the corresponding facts_fts row. Consequently, methods documented as permanently deleting facts or cleaning up stale information do not erase all copies of the content.

Parameterized SQL protects these paths from SQL in ...[truncated 1630 chars]

Remediation
View remediation

Remediation Suggestions

  1. Delete the corresponding FTS row in the same transaction as the base fact:
python
def forget(self, fact_id: str):
    conn = sqlite3.connect(self.db_path)
    try:
        cursor = conn.cursor()
        cursor.execute("SELECT rowid FROM facts WHERE id = ?", (fact_id,))
        row = cursor.fetchone()

        if row:
            cursor.execute("DELETE FROM facts_fts WHERE rowid = ?", (row[0],))
            cursor.execute("DELETE FROM facts WHERE id = ?", (fact_id,))

        conn.commit()
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()
  1. For bulk cleanup, collect the affected rowid values and delete their FTS rows before deleting the base rows, all within one transaction.

  2. Prefer an external-content FTS5 table tied to facts, with insert, update, and delete triggers that keep both structures synchronized automatically.

  3. Add regression tests that:

    • Store a fact.
    • Invoke forget() and forget_stale() separately.
    • Query both facts and facts_fts.
    • Assert that neither table retains the deleted content.
  4. Provide a migration or repair routine that removes existing orphaned FTS rows and rebuilds the index from active facts.

  5. Document that logical deletion from SQLite does not necessarily guarantee forensic erasure from database pages, journals, WAL files, or backups. If strong erasure is required, use appropriate SQLite secure-deletion settings and securely manage backups and journal files.

  6. As defense in depth for the plaintext database, create the default memory directory with mode 0700 and the database with mode 0600.

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 (5)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The README explicitly promotes persistent storage of facts, lessons, entities, and personal preferences across sessions, including examples about bosses, coworkers, and user preferences, but it provides no privacy, consent, retention, or sensitive-data handling warning. In an agent-memory skill, this omission is materially risky because users may store personal or third-party data indefinitely in a local database without understanding the privacy implications.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill explicitly implements persistent storage of facts, lessons learned, and entity information across sessions, but the description does not warn users that conversation-derived data may be retained on disk by default. This can lead to unintended collection and long-term storage of sensitive personal, project, or credential-adjacent information, especially when agents are instructed to automatically save context at session end.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This module persistently stores conversational facts, lessons, and entities to a local SQLite database under the user's home directory, but provides no user-facing notice, consent flow, or retention controls at initialization. In an agent skill context, this can silently retain sensitive personal or organizational data across sessions, increasing privacy and compliance risk if users do not realize memory is being written to disk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The forget and forget_stale methods permanently delete facts from the database, including bulk deletion of stale records, but provide no confirmation prompt or visible user disclosure at the point of deletion. The docstrings are developer-oriented and do not provide a user-facing warning that stored data may be irreversibly removed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The README documents destructive operations such as deleting facts and automatic cleanup of stale memories, but it does not warn that these actions can permanently remove data or alter agent memory state. In a persistent-memory tool, that can cause accidental loss of important records, especially if users assume cleanup is reversible or fully safe.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.