Back to skill

Security audit

Memory Tools

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent local memory plugin, but it has review-worthy risks around persistent instruction injection and insufficient file-path validation.

Install only if you are comfortable with a local plaintext memory store that can persist personal facts, preferences, contacts, and standing instructions. Leave autoInjectInstructions and autoMigrateLegacy disabled unless you explicitly need them, review stored instruction memories before enabling injection, protect the memory folder with filesystem permissions, and prefer an updated version that validates memory IDs and migration input paths.

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

T02 · Agent Memory Poisoning

Error
Location
src/index.ts:171
Finding
Persistent Agent Memory Poisoning Through Untrusted Standing Instructions<![CDATA[ ## Vulnerability Details **File Location**: `src/index.ts:171-184` **Vulnerability Type**: Persistent prompt injection through stored memory **Risk Level**: High ### Vulnerable Code ```ts if (cfg.autoInjectInstructions === true) { api.on('before_agent_start', async (event: { prompt?: string }) => { const instructions = store.getByCategory('instruction', 10); if (instructions.length === 0) { return undefined; } const instructionList = instructions .map((m: { content: string }) => `- ${m.content}`) .join('\n'); api.logger.info?.(`memory-tools: injecting ${instructions.length} standing instructions`); return { prependContext: `<standing-instructions>\nRemember these user instructions:\n${instructionList}\n</standing-instructions>`, }; }); } ``` ### Technical Analysis When `autoInjectInstructions` is enabled, the plugin retrieves persisted memories in the `instruction` category and inserts their content verbatim into the context supplied before agent startup. The stored content is not escaped, constrained to a safe instruction grammar, labeled with its provenance, or treated as untrusted data. An attacker-controlled memory can therefore contain directives such as instructions to ignore later requests, misuse tools, disclose data, or close the pseudo-XML delimiter and introduce new context sections. Because the content is persisted to disk, the malicious instruction can continue to influence future conversations rather than only the session in which it was stored. This is best classified as agent memory poisoning rather than direct modification of the Skill's static instruction text. The affected feature is documented and disabled by default, which reduces default exposure but does not address the unsafe trust transition when it is enabled. ### Attack Path 1. The administrator enables `autoInjectInstructions`. 2. An attacker causes attacker-controlled text to be passed to `memory_store` ...[truncated 1074 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not automatically promote free-form memory content into agent instructions. 2. Require explicit, authenticated user approval before a memory can enter the `instruction` category or become eligible for automatic injection. 3. Record and enforce provenance, including the originating user, channel, message, and approval status. 4. Represent standing instructions using a constrained structured schema rather than arbitrary natural-language content. 5. Escape or reject structural delimiters and markup that can terminate or alter the surrounding context block. 6. Inject stored entries as clearly labeled untrusted reference data, not as authoritative system-level instructions. 7. Apply strict size, character, and content limits. 8. Provide users with an auditable list of active standing instructions and a way to revoke them. 9. Add tests covering closing tags, instruction-confusion payloads, cross-session persistence, and memories derived from untrusted external content. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
src/file-manager.ts:126
Finding
Unvalidated Memory IDs Permit Filesystem Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `src/file-manager.ts:126-141` **Related Input Locations**: `src/tools.ts:166-168`, `src/tools.ts:230-232` **Vulnerability Type**: Path traversal through unrestricted memory identifiers **Risk Level**: High ### Vulnerable Code ```ts private getFilePath(id: string, category: MemoryCategory): string { return path.join(this.memoriesPath, this.categoryToDir(category), `${id}.md`); } /** * Get a memory by ID */ get(id: string): Memory | null { // Try to find in any category directory const categories: MemoryCategory[] = ['fact', 'preference', 'event', 'relationship', 'context', 'instruction', 'decision', 'entity']; for (const category of categories) { const filePath = this.getFilePath(id, category); if (fs.existsSync(filePath)) { return this.readMemoryFile(filePath); } ``` The update tool accepts the ID as an unrestricted string: ```ts parameters: Type.Object({ id: Type.String({ description: 'ID of memory to update (from memory_search results)' }), ``` The forget tool similarly accepts an unrestricted string: ```ts parameters: Type.Object({ id: Type.Optional(Type.String({ description: 'ID of memory to delete (if known)' })), ``` ### Technical Analysis Memory identifiers received from tool calls are directly appended with `.md` and passed to `path.join`. No UUID validation, path canonicalization, separator rejection, or destination-containment check is performed. An identifier containing `../` segments can therefore cause the resolved path to leave a memory category directory. The `get()` method checks the resulting path and attempts to parse any matching Markdown file as a memory. Subsequent update and delete operations also trust metadata parsed from the selected file. A reachable Markdown file with valid memory frontmatter can consequently be read and potentially rewritten or removed through the memory APIs. A specially crafted file can place traversal s ...[truncated 1529 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require all externally supplied memory IDs to match the exact identifier format generated by the plugin, preferably a canonical UUID: ```ts const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; ``` 2. Reject IDs containing `/`, `\`, null bytes, dot segments, encoded separators, or unexpected extensions. 3. Resolve every candidate path and verify containment before accessing it: ```ts const base = path.resolve(expectedCategoryDirectory); const candidate = path.resolve(base, `${id}.md`); if (!candidate.startsWith(`${base}${path.sep}`)) { throw new Error('Invalid memory path'); } ``` 4. Validate that the frontmatter ID matches the filename and that the category matches the containing directory. 5. Apply a strict runtime schema to all parsed frontmatter fields. 6. Consider using a trusted ID-to-path index rather than searching the filesystem using caller-provided identifiers. 7. Protect against symlink traversal by using appropriate `lstat`, real-path containment, and no-follow file operations where supported. 8. Add negative tests for absolute paths, `../`, backslash traversal, malformed UUIDs, symlink escapes, and malicious frontmatter IDs. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
src/migration.ts:217
Finding
Legacy Migration Uses Untrusted Database IDs as Output File Paths<![CDATA[ ## Vulnerability Details **File Location**: `src/migration.ts:217-228` **Related Data Origin**: `src/migration.ts:105-123` **Vulnerability Type**: Arbitrary out-of-directory file write during legacy migration **Risk Level**: High ### Vulnerable Code Legacy database values are assigned directly to the new memory object: ```ts const memory: Memory = { id: row.id, content: row.content, category: row.category as MemoryCategory, confidence: row.confidence ?? 0.8, importance: row.importance ?? 0.5, createdAt: row.created_at, updatedAt: row.updated_at, lastAccessedAt: row.last_accessed_at ?? row.created_at, decayDays: row.decay_days, sourceChannel: row.source_channel ?? undefined, sourceMessageId: row.source_message_id ?? undefined, tags: JSON.parse(row.tags || '[]'), supersedes: row.supersedes ?? undefined, deletedAt: undefined, deleteReason: undefined, }; ``` The imported ID is then used directly in an output path: ```ts function writeMemoryFile(fileManager: MemoryFileManager, memory: Memory): void { // Use internal method to write with existing ID const memoriesPath = fileManager.getMemoriesPath(); const categoryDir = getCategoryDir(memory.category); const filePath = path.join(memoriesPath, categoryDir, `${memory.id}.md`); writeMemoryToFile(memory, filePath); } ``` The deleted-memory migration follows the same pattern: ```ts const filePath = path.join(deletedPath, `${memory.id}.md`); writeMemoryToFile(memory, filePath); ``` ### Technical Analysis The migration process treats the legacy SQLite database as trusted. In particular, it casts the database category to `MemoryCategory` and uses `row.id` as a filename without validating either value. If a legacy row contains path traversal components in its ID, `path.join` can resolve the output beyond the configured memory destination. `writeMemoryToFile()` then creates parent directories recursively and writes the generated Markdown content using `fs.writeFil ...[truncated 1678 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every imported ID against the canonical UUID format before constructing a path. 2. Generate a new UUID for invalid legacy IDs and maintain an explicit old-to-new ID mapping for references such as `supersedes`. 3. Validate the category against the exact `MEMORY_CATEGORIES` allowlist before calling `getCategoryDir`. 4. Resolve the destination path and verify that it remains beneath the expected category or `.deleted` directory. 5. Use exclusive file creation or an explicit conflict policy to avoid silently overwriting existing files. 6. Reject symlinked destination directories or verify containment using real paths immediately before writing. 7. Treat legacy databases as untrusted input and validate field types, lengths, timestamps, tags, and content sizes. 8. Perform migration into a temporary staging directory, validate the complete output set, and atomically move it into place only after successful verification. 9. Add migration tests containing traversal IDs, absolute paths, invalid categories, duplicate IDs, symlink destinations, and malformed database fields. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (48)

Known Vulnerable Dependency: vitest==2.1.9 — 2 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)

Critical
Category
Supply Chain
Confidence
90% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: vitest==2.1.9 — 2 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)

Critical
Category
Supply Chain
Confidence
95% confidence
Finding
The manifest allows a Vitest version flagged with critical advisories involving arbitrary file read/execution and path traversal in test-related components. Because Vitest is a devDependency, exposure is mainly to developer and CI environments rather than plugin runtime users, but in those contexts it can still lead to source disclosure or code execution if vulnerable features are used.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description omits materially relevant behavior such as automatic instruction injection at conversation start, lifecycle-hook execution, migration behavior, and a broader CLI surface. Hidden or under-documented behavior reduces informed consent and can cause operators to enable a plugin without understanding that it can inject prior instructions or process legacy data automatically.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description omits materially relevant behavior such as automatic instruction injection at conversation start, lifecycle-hook execution, migration behavior, and a broader CLI surface. Hidden or under-documented behavior reduces informed consent and can cause operators to enable a plugin without understanding that it can inject prior instructions or process legacy data automatically.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description omits materially relevant behavior such as automatic instruction injection at conversation start, lifecycle-hook execution, migration behavior, and a broader CLI surface. Hidden or under-documented behavior reduces informed consent and can cause operators to enable a plugin without understanding that it can inject prior instructions or process legacy data automatically.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The description omits materially relevant behavior such as automatic instruction injection at conversation start, lifecycle-hook execution, migration behavior, and a broader CLI surface. Hidden or under-documented behavior reduces informed consent and can cause operators to enable a plugin without understanding that it can inject prior instructions or process legacy data automatically.

Known Vulnerable Dependency: brace-expansion==2.0.2 — 4 advisory(ies): CVE-2026-13149 (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding); CVE-2026-33750 (brace-expansion: Zero-step sequence causes process hang and memory exhaustion); CVE-2026-14257 (brace-expansion: DoS via unbounded expansion length causing an out-of-memory pro) +1 more

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: minimatch==9.0.5 — 3 advisory(ies): CVE-2026-27904 (minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regu); CVE-2026-26996 (minimatch has a ReDoS via repeated wildcards with non-matching literal in patter); CVE-2026-27903 (minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adja)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: nanoid==3.3.11 — 3 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2026-73086 (nanoid: Integer Overflow or Wraparound)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: postcss==8.5.6 — 4 advisory(ies): CVE-2026-45623 (PostCSS: Arbitrary file read and information disclosure via attacker-controlled ); CVE-2026-69153 (PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappi); CVE-2026-41305 (PostCSS has XSS via Unescaped </style> in its CSS Stringify Output) +1 more

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: rollup==4.57.1 — 1 advisory(ies): CVE-2026-27606 (Rollup 4 has Arbitrary File Write via Path Traversal)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Known Vulnerable Dependency: vite==5.4.21 — 3 advisory(ies): CVE-2026-39365 (Vite Vulnerable to Path Traversal in Optimized Deps `.map` Handling); CVE-2026-53571 (vite: `server.fs.deny` bypass on Windows alternate paths); CVE-2026-53632 (launch-editor: NTLMv2 hash disclosure via UNC path handling on Windows)

High
Category
Supply Chain
Confidence
80% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Memory Manipulation

High
Category
Memory Poisoning
Content
expect(updated!.updatedAt).toBeGreaterThanOrEqual(created.updatedAt);
  });

  it('should soft delete memory', () => {
    const created = fileManager.create({
      content: 'To be deleted',
      category: 'fact',
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Memory Manipulation

High
Category
Memory Poisoning
Content
expect(updated!.updatedAt).toBeGreaterThanOrEqual(created.updatedAt);
  });

  it('should soft delete memory', () => {
    const created = fileManager.create({
      content: 'To be deleted',
      category: 'fact',
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Missing User Warnings

High
Confidence
98% confidence
Finding
This logic auto-deletes an existing memory based only on similarity score and category match, without explicit user approval or even surfacing the conflict for review. Because semantic similarity is heuristic and attacker-influenceable, a malicious or mistaken input can overwrite or erase legitimate facts, preferences, or standing instructions, leading to persistent memory poisoning and loss of integrity.

Memory Manipulation

High
Category
Memory Poisoning
Content
},

    // ═══════════════════════════════════════════════════════════════════════
    // FORGET - Delete memory
    // ═══════════════════════════════════════════════════════════════════════
    memory_forget: {
      name: 'memory_forget',
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Missing User Warnings

High
Confidence
99% confidence
Finding
The forget tool can delete a memory directly from a fuzzy search result when heuristics indicate an exact match or clear winner, without requiring explicit selection of the target ID. This enables accidental or adversarial deletion through ambiguous phrasing, semantic collisions, or prompt-manipulated queries, and the deletion appears irreversible.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
'event',        // "User has dentist appointment Tuesday"
  'relationship', // "User's sister is named Sarah"
  'context',      // "User is working on a React project"
  'instruction',  // "Always respond in Spanish"
  'decision',     // "We decided to use PostgreSQL"
  'entity',       // Contact info, phone numbers, emails
] as const;
Confidence
70% confidence
Finding
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
'event',        // "User has dentist appointment Tuesday"
  'relationship', // "User's sister is named Sarah"
  'context',      // "User is working on a React project"
  'instruction',  // "Always respond in Spanish"
  'decision',     // "We decided to use PostgreSQL"
  'entity',       // Contact info, phone numbers, emails
] as const;
Confidence
70% confidence
Finding
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
'event',        // "User has dentist appointment Tuesday"
  'relationship', // "User's sister is named Sarah"
  'context',      // "User is working on a React project"
  'instruction',  // "Always respond in Spanish"
  'decision',     // "We decided to use PostgreSQL"
  'entity',       // Contact info, phone numbers, emails
] as const;
Confidence
70% confidence
Finding
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
'event',        // "User has dentist appointment Tuesday"
  'relationship', // "User's sister is named Sarah"
  'context',      // "User is working on a React project"
  'instruction',  // "Always respond in Spanish"
  'decision',     // "We decided to use PostgreSQL"
  'entity',       // Contact info, phone numbers, emails
] as const;
Confidence
70% confidence
Finding
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The documentation explains local storage, deletion, and export workflows, but it does not prominently warn that the plugin may persist highly sensitive user data such as personal preferences, contacts, instructions, and context in readable local files and exports. Because memories are stored as plaintext markdown and can be listed or exported, users may underestimate confidentiality risk, leading to accidental disclosure through backups, shell history, shared machines, or filesystem access.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The README gives conflicting statements about legacy migration: earlier sections say migration only occurs when `autoMigrateLegacy=true`, but the migration section says v2 automatically detects and migrates v1 databases with no manual action. In a memory plugin handling potentially sensitive local data, this ambiguity can cause operators to enable or deploy the plugin under false assumptions about when legacy data will be copied into the new store.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill explicitly stores potentially sensitive user memories locally and supports export, but does not prominently warn about privacy, retention, sensitivity of stored content, or risks of exporting personal data. This can lead users or operators to persist confidential information without appropriate safeguards or informed consent.