Back to skill

Security audit

evermind-ai-agent-memory

Security checks across malware telemetry and agentic risk

Overview

This is a local memory helper, but it needs review because it can persist agent rules from discovered project files and write or delete local state without tight safeguards.

Install only in repositories you trust. Run the preview/list mode first, inspect .evermind/discovery.json before accepting discovered rules or identity files, keep generated outputs inside .evermind, and review config.yaml for absolute paths or ../ traversal before running the indexer. Do not enable the wildcard PreToolUse hook until .evermind/rules.json has been reviewed, and remember that handover notes are overwritten and deleted after recovery.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (3)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:70
Finding
Automatically Discovered Project Files Can Hijack Agent Instructions and Poison Persistent Rules<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:70-72`; `scripts/memory_index.py:48-55, 292-316` **Vulnerability Type**: Instruction trust-boundary violation and persistent memory poisoning **Risk Level**: High ### Vulnerable Code and Instructions From `SKILL.md:70-72`: ```markdown 1. **Step 0 — Discover (first time or when sources moved)**: run `python scripts/memory_index.py --discover .` — it locates your memory carriers by common conventions (candidate list lives in the script header constants) and writes `.evermind/discovery.json`. Python unavailable? Fall back to the hand-list below (derived from the script; the script is authoritative). At every recovery, first **stat the stored paths** — any missing/unreadable source triggers re-discovery (never reuse stale paths). 2. **Read the L3 must-read files**: roles resolved in step 0/1 (rules, identity, todos, journal) + `must_read_extra`. Every one, no shortcuts. Host injected identity (Hermes/OpenClaw)? Mark `identity ✅ (host)` and skip the file probe. 2b. **Rules alignment**: from the rules file(s) just read, extract every imperative rule the user stated (do X / never do Y). List them explicitly in your recovery report — this is the moment the user experiences as *"it remembered what I told it"*. Then write/refresh `.evermind/rules.json` (id, text, keywords per rule) so the guardrail can enforce them at action time (see Rules & guardrails). If the rules file is empty or has no imperative rules, say "no hard rules found" honestly. ``` From `scripts/memory_index.py:48-55`: ```python RULES_CANDIDATES = [ "CLAUDE.md", "AGENTS.md", "AGENTS.txt", ".cursor/rules/", # dir ] RULES_CANDIDATES_HOME = [ ".claude/CLAUDE.md", ".config/agent/rules.md", ".claude/rules.md", ] ``` From `scripts/memory_index.py:292-316`: ```python def discover(scan_root, mode="auto"): """Locate role carriers. Returns {role: [paths...]} + missing list. Only paths — never reads file content.""" root = ex ...[truncated 3233 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require explicit user approval before treating an automatically discovered file as a trusted rules or identity source. 2. Present discovered paths and proposed extracted rules for confirmation before loading or persisting them. 3. Record provenance for every rule, including source path, scope, author or approval status, extraction time, and content hash. 4. Separate project instructions from user-level safety policy. Do not automatically treat project files as user-authored global rules. 5. Reject or quarantine rules that attempt to: - Override higher-priority instructions. - Disable safety controls or approval requirements. - Change instruction precedence. - Authorize credential access, destructive actions, or external transmission. - Expand the Agent’s permissions. 6. Scope persisted rules to a canonical project identifier rather than sharing unqualified state across contexts. 7. Never overwrite `.evermind/rules.json` without showing a diff and obtaining approval for newly discovered or materially changed rules. 8. Treat `identity` and `rules` as distinct trust domains instead of assigning one discovered file to both roles automatically. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/rule_gate.py:70
Finding
PreToolUse Guardrail Can Be Bypassed Through Uninspected Fields and Fail-Open Parsing<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rule_gate.py:70-85, 109-115`; hook configuration in `SKILL.md:125-143` **Vulnerability Type**: Incomplete security-control mediation and fail-open input handling **Risk Level**: Medium ### Vulnerable Code From `scripts/rule_gate.py:70-85`: ```python def _compose_from_stdin() -> str: """Claude Code PreToolUse hook payload → one searchable description.""" raw = sys.stdin.read() if not sys.stdin.isatty() else "" if not raw.strip(): return "" # no payload → pass (fail open) try: obj = json.loads(raw) except Exception: return "" # malformed payload → pass (fail open) tool = str(obj.get("tool_name", "")) ti = obj.get("tool_input") or {} parts = [tool] if isinstance(ti, dict): for k in ("command", "description", "prompt", "text", "query", "path"): v = ti.get(k) if v: parts.append(str(v)) return " ".join(parts) ``` From `scripts/rule_gate.py:109-115`: ```python else: desc = _compose_from_stdin() if not desc: return 0 # hook with no/empty payload → pass (fail open) hits = check(desc, load_rules()) if hits: print("⛔ action blocked — violates a rule you set:", file=sys.stderr) ``` The recommended configuration in `SKILL.md` applies the hook to every tool: ```json { "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "python ~/.claude/skills/evermind/scripts/rule_gate.py" } ] } ] } } ``` ### Technical Analysis Although the recommended hook matcher covers every tool, the gate only examines six top-level `tool_input` fields: - `command` - `description` - `prompt` - `text` - `query` - `path` It ignores other security-relevant fields such as `content`, `file_path`, `new_string`, `old_string`, `url`, recipient fields, hea ...[truncated 1864 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse and validate the complete documented hook schema rather than selecting a small fixed set of fields. 2. Recursively canonicalize all scalar values in `tool_input`, including nested objects and arrays, as a minimum compatibility measure. 3. Prefer tool-specific structured rules over keyword matching. For example: - File-deletion rules should inspect canonical operation type and target paths. - External-message rules should inspect tool name, destination, recipient, and payload. - Write restrictions should inspect `file_path`, `content`, and edit fields. 4. Fail closed for protected tool classes when payload parsing fails, the schema is unsupported, or configured rules cannot be loaded. 5. Distinguish “no applicable rule” from “security control unavailable” and report the latter prominently. 6. Validate rule-file integrity and permissions; do not silently disable enforcement when it is corrupt. 7. Normalize command aliases, shell syntax, Unicode, case, paths, and encoded arguments before policy evaluation. 8. Add regression tests for Write, Edit, Bash, HTTP, messaging, nested payloads, malformed JSON, missing fields, and alternate representations of prohibited actions. 9. Document that keyword matching is advisory unless comprehensive structured enforcement is implemented. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/memory_index.py:575
Finding
Configuration-Controlled Output Paths Allow Overwriting Arbitrary Writable Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/memory_index.py:456-459, 575-583` **Vulnerability Type**: Unrestricted file-write destination and unsafe file replacement **Risk Level**: High ### Vulnerable Code From `scripts/memory_index.py:456-459`: ```python with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines) + "\n") with open(state_path, "w", encoding="utf-8") as f: json.dump(new_state, f, ensure_ascii=False, indent=1) ``` From `scripts/memory_index.py:575-583`: ```python out_dir = cfg.get("output_dir") or os.path.dirname(os.path.abspath(args.config or "config.yaml")) if not os.path.isabs(out_dir): out_dir = os.path.join(os.path.dirname(os.path.abspath(args.config or "config.yaml")), out_dir) os.makedirs(out_dir, exist_ok=True) build(os.path.join(out_dir, cfg.get("output_index", "memory_index.md")), os.path.join(out_dir, cfg.get("output_state", "memory_index_state.json")), files, lang) return 0 ``` ### Technical Analysis The values of `output_dir`, `output_index`, and `output_state` are read from configuration and used to construct write targets without enforcing an allowed directory. The implementation does not reject: - Absolute paths. - Parent-directory traversal. - Symlink targets. - Collisions with existing project files. - Identical index and state paths. - Destinations outside the project or `.evermind` directory. Both destinations are opened in `"w"` mode, which truncates existing files before writing. If `output_index` or `output_state` is absolute, `os.path.join()` discards the preceding `out_dir`. Relative values containing `..` can similarly escape the intended directory. The writes are also non-atomic. A crash between truncation and completion can leave partial files, and a local race involving symlink replacement can redirect the write. ### Attack Path 1. An attacker supplies or modifies a project’s `config.yaml`. 2. The atta ...[truncated 1255 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a dedicated output directory such as `<project>/.evermind` and require all output files to remain beneath its canonical path. 2. Reject absolute values for `output_index` and `output_state`. 3. Resolve paths with `realpath()` or `pathlib.Path.resolve()` and verify containment using a path-aware comparison rather than string-prefix matching. 4. Reject `..` traversal components and output paths that resolve outside the allowed directory. 5. Refuse to write through symlinks. Validate every existing path component and use operating-system facilities such as `O_NOFOLLOW` where available. 6. Detect collisions with input files, configuration files, discovered memory sources, and between the two output destinations. 7. Write to securely created temporary files inside the destination directory, flush and synchronize them, then use atomic replacement. 8. Apply restrictive permissions to state files where appropriate. 9. Require explicit user confirmation before replacing an existing non-Evermind file. 10. Add tests covering absolute paths, traversal, symlink destinations, duplicate output paths, interrupted writes, and output/input collisions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill clearly instructs the agent to read and write local files (`.evermind/discovery.json`, `memory_index.md`, `.evermind/rules.json`, `.evermind/handover.md`) but does not declare permissions or boundaries. That mismatch can cause the host or user to underestimate the skill’s filesystem reach, increasing the chance of unintended reads/writes to sensitive local state.

Vague Triggers

Medium
Confidence
91% confidence
Finding
Using the plain word "handover" as an action trigger is collision-prone because it can appear in normal conversation, planning, or documentation rather than as an intentional command. In this skill's context, that can cause unintended writes to `.evermind/handover.md`, unexpected state transitions, or persistence of sensitive task context at the wrong time.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The README markets seamless recovery but does not prominently and plainly disclose early on that each session reads identity, rules, profile, todos, and work-log memory files. For a memory-oriented skill, incomplete disclosure increases the risk of users enabling broad, repeated access to sensitive local data without informed consent or proper scoping.

Vague Triggers

Medium
Confidence
87% confidence
Finding
Using a broad natural-language trigger like `handover` can collide with ordinary conversation, causing the skill to create or overwrite handover state when the user did not intend to invoke the workflow. Because this skill persists state and later deletes that note, accidental invocation has real side effects beyond a harmless response.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill instructs the agent to delete `.evermind/handover.md` automatically after reading it, but the destructive behavior is normalized as part of routine recovery rather than surfaced as a clear user-visible deletion policy. Automatic deletion of persisted state can lead to loss of auditability, accidental loss of context, or removal of information the user expected to keep.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger phrase "handover" is common natural language and can easily appear in ordinary conversation, causing the skill to activate unintentionally. In a memory/persistence skill, accidental activation can create or overwrite session summaries, leak contextual state into persistent artifacts, or steer later sessions based on unintended handoff data.

Session Persistence

Medium
Category
Rogue Agent
Content
Evermind treats your rules as **memory too** — the most important kind. Two layers make them stick:

**Alignment layer (every session).** At recovery (step 2b) the agent restates the imperative rules it found in your rules files — do's and don'ts alike. You see, in plain text, that what you set is loaded. No rule file yet? Write your rules in `CLAUDE.md`/`AGENTS.md`/your rules file — plain sentences work: *"Never delete files without asking."* *"Always reply in Chinese."* *"Ask before sending anything externally."*

**Enforcement layer (where the platform supports it).** Prohibitions you never want broken ("never", "don't", "always ask first") get written to `.evermind/rules.json` (id / text / keywords), and a tiny local gate script checks every action before it runs:
Confidence
89% confidence
Finding
The skill persists behavioral rules across sessions in `CLAUDE.md`/`AGENTS.md` and `.evermind/rules.json`, then re-applies them automatically in future sessions and optional tool hooks. Persistent rule memory can become dangerous if sensitive, outdated, maliciously injected, or context-inappropriate instructions are carried forward without fresh user review, especially when tied to action-time enforcement.

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

No suspicious patterns detected.