Back to skill

Security audit

Agent Memory Store

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real persistent memory store, but it exposes private memory data through an unauthenticated network service and can send memory text to OpenAI without clear user-facing disclosure.

Review before installing or running this skill. Only run it on a trusted, isolated host unless authentication, loopback binding, owner-level authorization, deletion controls, request limits, and clear opt-in for OpenAI embeddings are added. Do not store secrets or sensitive user data in it as currently implemented.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T02 · Agent Memory Poisoning

Error
Location
scripts/memory_store.py:96
Finding

Unauthenticated Persistent Agent Memory Poisoning

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/memory_store.py:80
Finding

Unauthenticated Disclosure and Deletion of Private Memories

Content
View full analysis
?)" params = [now] if agent: sql += " AND (owner=? OR public=1)"; params.append(agent) with _lock: rows_raw = db.execute(sql, params).fetchall() pairs = [(self._row(r), bytes(r["embedding"]) if r["embedding"] else None) for r in rows_raw] if query: qemb = embed_openai(query) if OPENAI_KEY else None scored = [] for d, emb in pairs: s = cosine(qemb, emb) if qemb and emb else jaccard(query, d["content"]) scored.append((s, d)) scored.sort(key=lambda x: -x[0]) rows = [d for _, d in scored] else: rows = [d for d, _ in pairs] return self._send(200, {"memories": rows[:limit], "total": len(rows)}) if p.path.startswith("/memories/") and len(p.path.split("/"))==3: mid = p.path.split("/")[-1] with _lock: row = db.execute("SELECT * FROM memories WHERE id=? AND (expires_at IS NULL OR expires_at>?)",(mid,now)).fetchone() if not row: return self._send(404, {"error":"not found"}) return self._send(200, {"memory": self._row(row)}) ``` ```python if p.path.startswith("/memories/") and p.path.endswith("/delete"): mid = p.path.split("/")[-2] with _lock: get_db().execute("DELETE FROM memories WHERE id=?",(mid,)); get_db().commit() return self._send(200, {"deleted":mid}) ``` ### Technical Analysis No endpoint authenticates the caller. When `GET /memories` is requested without an `agent` parameter, no owner or `public` condition is added to the SQL query. Consequently, the endpoint returns all u ...[truncated 2067 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/memory_store.py:35
Finding

Undisclosed External Transmission of Memory Content and Search Queries

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/memory_store.py:81
Finding

Unbounded Request Processing and Dataset-Wide Search Enable Denial of Service

Content
View full analysis
?)" params = [now] if agent: sql += " AND (owner=? OR public=1)"; params.append(agent) with _lock: rows_raw = db.execute(sql, params).fetchall() pairs = [(self._row(r), bytes(r["embedding"]) if r["embedding"] else None) for r in rows_raw] if query: qemb = embed_openai(query) if OPENAI_KEY else None scored = [] for d, emb in pairs: s = cosine(qemb, emb) if qemb and emb else jaccard(query, d["content"]) scored.append((s, d)) scored.sort(key=lambda x: -x[0]) rows = [d for _, d in scored] else: rows = [d for d, _ in pairs] return self._send(200, {"memories": rows[:limit], "total": len(rows)}) ``` ```python length = int(self.headers.get("Content-Length",0)) body = json.loads(self.rfile.read(length) or b"{}") ``` ```python mid = str(uuid.uuid4())[:8] ttl = body.get("ttl_seconds") emb = embed_openai(body["content"]) with _lock: get_db().execute("INSERT INTO memories VALUES (?,?,?,?,?,?,?,?)", (mid, body["owner"], body["content"], json.dumps(body.get("tags",[])), 1 if body.get("public") else 0, time.time(), time.time()+ttl if ttl else None, emb)) ``` ### Technical Analysis The server trusts the request's `Content-Length` and reads that many bytes without an upper bound. It does not impose maximum lengths on memory content, owners, tags, or search queries. Because memory creation is unauthenticated and unmetered, a caller can repeatedly insert large records and grow the persistent database. The listing and search implementation retrieves every matching database row with `fetchall()` b ...[truncated 1994 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

Tainted flow: 'req' from os.getenv (line 39, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/memory_store.py (reported line 42)May include surrounding context.

python
req = urllib.request.Request(
            "https://api.openai.com/v1/embeddings", data=payload,
            headers={"Authorization": f"Bearer {OPENAI_KEY}", "Content-Type": "application/json"})
        with urllib.request.urlopen(req, timeout=8) as r:
            vec = json.loads(r.read())["data"][0]["embedding"]
            return struct.pack(f"{len(vec)}f", *vec)
    except: return None

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented purpose understates materially sensitive behavior: an unauthenticated HTTP server, optional outbound calls to the OpenAI embeddings API, destructive delete functionality, and health/status disclosure. This mismatch can mislead users into deploying the skill without understanding its exposure, data flow, and attack surface, especially given that it persists cross-agent memory to disk.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill exposes network and environment-related capabilities but does not declare any tool scope or permissions boundary. That omission weakens reviewability and containment, making it easier for a skill to access networking or secrets in ways a user or platform operator did not explicitly approve.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill stores agent memories on disk and examples include potentially sensitive preference data, but the description does not clearly warn users about persistence or privacy implications. In a shared cross-agent memory context, this increases the chance that sensitive user or agent data is retained longer than expected and accessed by unintended parties.

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 · SKILL.md (reported line 24)May include surrounding context.

bash
# Store a memory
curl -X POST http://localhost:8768/memories \
  -H "Content-Type: application/json" \
  -d '{"owner":"my-agent","content":"user prefers SOL payments","ttl_seconds":86400,"public":false}'

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code introduces capabilities beyond a simple local memory store by reading an API key and making outbound network requests to a third party. While not inherently malicious, this broader capability increases data-exposure risk because memory contents and queries may leave the local environment.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill advertises a shared memory store but also sends stored/query text to OpenAI for embeddings, which expands the data-handling scope beyond local persistence. In a memory system, this can expose potentially sensitive cross-agent data to an external service without clear boundary enforcement or explicit disclosure at the API level.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Memory content is transmitted to the OpenAI embeddings API without any user-facing warning or consent mechanism at the point of use. Because a memory store may contain sensitive agent data, this silent external transmission creates privacy and compliance risk even if the destination is legitimate.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
87% confidence
Finding

The code performs external transmission to OpenAI's API, sending text derived from stored memories or queries off-host. In the context of an agent memory store, this is a meaningful privacy and data-boundary concern because the service processes potentially sensitive shared content.

Content

Scanner excerpt · scripts/memory_store.py (reported line 40)May include surrounding context.

python
try:
        payload = json.dumps({"input": text[:2000], "model": "text-embedding-3-small"}).encode()
        req = urllib.request.Request(
            "https://api.openai.com/v1/embeddings", data=payload,
            headers={"Authorization": f"Bearer {OPENAI_KEY}", "Content-Type": "application/json"})
        with urllib.request.urlopen(req, timeout=8) as r:
            vec = json.loads(r.read())["data"][0]["embedding"]

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The delete endpoint irreversibly removes memories with no confirmation, soft-delete, or access control. Combined with the unauthenticated network service, this enables trivial destructive actions by any reachable party, causing loss of shared state and denial of service to dependent agents.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

The server binds to 0.0.0.0 and exposes read/write/delete memory operations without any authentication or authorization checks. In a shared-memory context this is especially dangerous because any reachable client can enumerate, insert, or delete memories across agents, undermining confidentiality and integrity.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.