Back to skill

Security audit

元习 yotta-learn

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent learning-note purpose, but its hooks and promotion commands can persist or replay learned content too broadly, including into future agent instructions.

Review before installing. Use the core CLI only in trusted projects, keep .learnings free of secrets, avoid installing the provided broad prompt hooks unless you narrow their matchers, and manually inspect any entry before promoting it into AGENTS.md or CLAUDE.md. Use --remember only when the yotta-memory executable on PATH is trusted, and install only into directories you control that do not contain attacker-created symlinks.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/yotta_learn.py:425
Finding
Unrestricted promotion target permits arbitrary file overwrite## Vulnerability Details **File Location**: `scripts/yotta_learn.py:425-445` **Vulnerability Type**: Unrestricted path traversal and arbitrary file write **Risk Level**: Medium ### Technical Analysis The `promote` command accepts `--to` as an unrestricted path and joins it directly to the selected learning directory. Python's `pathlib` discards the base path when the right operand is absolute. Relative paths containing `../` can likewise escape the intended directory. The destination is then passed to `_atomic_write_text`, which creates missing parent directories and replaces an existing file. There is no canonical-path containment check and no allowlist restricting the destination to `AGENTS.md` or `CLAUDE.md`. ```python target_name = args.to if not target_name or target_name == "auto": target_name = "CLAUDE.md" if (directory / "CLAUDE.md").exists() else "AGENTS.md" target = directory / target_name old = _read_text(target) if entry.summary and entry.summary[:60] in old: print("[提示] 目标文件已包含相似内容,跳过(自动去重)") return 0 if not old.endswith("\n"): old += "\n" _atomic_write_text(target, old + block + "\n") ``` The unrestricted option is registered here: ```python p_promote.add_argument("--to", help="目标文件(默认 auto:CLAUDE.md 优先)") ``` This exceeds the minimum privileges needed to promote an entry into one of the two declared agent instruction files. It does not independently elevate operating-system privileges: the process remains limited to files writable by the invoking user. ### Attack Path 1. An attacker convinces a user or automation workflow to invoke `promote` with a crafted `--to` value, or controls parameters passed to the CLI. 2. The attacker supplies an absolute path or a traversal path such as `../../some-file`. 3. The command reads the existing destination, appends the generated promotion block, and atomically replaces the file. 4. If the destination is an agent configuration o ...[truncated 924 chars]
Remediation
## Remediation Suggestions - Remove arbitrary `--to` support unless it is essential. - Allow only the exact basenames `AGENTS.md` and `CLAUDE.md`. - Resolve both the base and destination paths and enforce containment with `Path.relative_to`. - Reject absolute paths, `..` components, symlinks, devices, and non-regular destination files. - Require explicit confirmation before modifying an existing instruction file. - Escape or delimit promoted learning text so it is treated as quoted data rather than agent instructions. - Add tests for absolute paths, traversal paths, symlink destinations, and existing-file overwrite behavior.

T02 · Agent Memory Poisoning

Warning
Location
hooks/claude-settings.json:2
Finding
Stored learning entries can be injected into every future agent prompt## Vulnerability Details **File Location**: `hooks/claude-settings.json:2-12` **Vulnerability Type**: Persistent untrusted-content injection into agent context **Risk Level**: Medium ### Technical Analysis The supplied Claude hook template registers `review` for every `UserPromptSubmit` event. The matcher is empty, so no prompt or task filtering is applied: ```json { "hooks": { "UserPromptSubmit": [ { "matcher": "", "hooks": [ { "type": "command", "command": "python3 PATH_TO_SKILL/scripts/yotta_learn.py review" } ] } ] } } ``` An identical template exists at `hooks/codex-settings.json:2-12`. The `review` command prints the summary of every pending or in-progress entry without marking it as untrusted data or escaping instruction-like text: ```python entries = [e for e in parse_entries(directory) if e.status in ("pending", "in_progress")] entries.sort(key=lambda e: (e.priority_rank(), e.logged)) if not entries: print("(无待处理条目)") return 0 for e in entries: print("%-18s %-9s %-8s %s" % (e.eid, e.priority, e.status, e.summary or "-")) ``` Learning messages are user-supplied strings and may originate from command output, external content, issue reports, or other untrusted sources. Consequently, a stored summary can contain prompt-injection instructions. Once the optional hook is installed, that content is repeatedly returned to the agent on every prompt submission. Hook installation is manual and documented, so this is not covert system persistence. The security issue is the absence of a trust boundary between stored observations and executable agent instructions. ### Attack Path 1. An attacker places instruction-like text in content processed by the user or agent. 2. That content is recorded as the first line of a learning entry through `log --message`. 3. The entry ...[truncated 1114 chars]
Remediation
## Remediation Suggestions - Do not inject all pending entries on every prompt by default. - Prefer a session-start notification that reports only entry identifiers and counts. - Require an explicit user action to load full summaries. - Wrap stored content in a clearly delimited, quoted data block with a warning that it is untrusted and must not be treated as instructions. - Sanitize or reject instruction-like summaries when entries originate from external content. - Track entry provenance and only auto-display entries explicitly created or approved by the user. - Add limits for output size, entry count, and entry age. - Document the prompt-injection risk in the hook setup guide. - Apply the same remediation to `hooks/codex-settings.json`.

T09 · Insecure Skill Coding Practices

Warning
Location
install.sh:63
Finding
Shell installer follows destination symlinks and can overwrite or delete unintended files## Vulnerability Details **File Location**: `install.sh:63-68` **Vulnerability Type**: Unsafe recursive copy and deletion through a user-controlled destination **Risk Level**: Medium ### Technical Analysis The shell installer constructs its installation target from a caller-controlled directory. It neither validates the canonical target nor checks whether the existing `yotta-learn` path is a symbolic link. ```bash install_to() { mkdir -p "$1/$SKILL_NAME" cp -r "$SOURCE_DIR/." "$1/$SKILL_NAME/" rm -rf "$1/$SKILL_NAME/.git" echo "installed -> $1/$SKILL_NAME" } ``` If `$1/yotta-learn` is a pre-existing symlink to another directory, `mkdir`, `cp`, and the `.git` removal can operate through that link. This allows package files to be copied over an unintended directory and can delete the `.git` directory of the symlink target. Unlike the Node.js installer, the shell installer also lacks a check preventing installation into or around the source directory. The quoting prevents shell command injection, but it does not prevent path traversal or symlink following. ### Attack Path 1. An attacker with access to a shared or otherwise writable installation parent creates `yotta-learn` as a symlink to a victim directory. 2. The victim runs `bash install.sh --dir` against that parent, or the default directory detection selects it. 3. `cp -r` follows the existing destination path and copies project files into the symlink target. 4. `rm -rf "$1/$SKILL_NAME/.git"` deletes the target directory's `.git` metadata if present. 5. Existing files with matching names may be overwritten, and the target repository may lose version-control metadata. ### Impact Assessment The installer can modify unintended files and delete a repository's `.git` directory within the invoking user's permissions. This may cause: - Loss of version-control metadata. - Overwrite of files in another project or user directory. - Installation of age ...[truncated 295 chars]
Remediation
## Remediation Suggestions - Resolve the canonical parent and destination paths before copying. - Reject an existing destination if it or any relevant path component is a symbolic link. - Ensure the destination is outside `SOURCE_DIR`. - Replace destructive in-place copying with installation into a new temporary sibling directory followed by a controlled rename. - Do not run `rm -rf` on a path derived from user input. Exclude `.git` while copying instead. - Refuse to overwrite an existing skill directory unless the user supplies an explicit upgrade flag. - During upgrades, verify that the destination contains an expected ownership marker before replacing it. - Add tests for symlinked destination directories, self-installation, traversal paths, and pre-existing repositories.

T09 · Insecure Skill Coding Practices

Warning
Location
bin/install.js:130
Finding
Node.js installer permits overwrite through symlinked destination entries## Vulnerability Details **File Location**: `bin/install.js:130-158` **Vulnerability Type**: Symlink-based arbitrary file overwrite **Risk Level**: Medium ### Technical Analysis The Node.js installer verifies only that the lexical target path is outside the package source directory. It does not inspect the real path or reject pre-existing symlinks in the destination tree. ```javascript function assertSafeTarget(target) { const rel = path.relative(PKG_ROOT, target); if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) { throw new UsageError('Target directory must be outside the skill source directory'); } } function copyDir(src, dst, skip) { for (const entry of fs.readdirSync(src, { withFileTypes: true })) { if (skip.has(entry.name)) continue; const from = path.join(src, entry.name); const to = path.join(dst, entry.name); try { if (entry.isDirectory()) { fs.mkdirSync(to, { recursive: true }); copyDir(from, to, skip); } else if (entry.isFile()) { fs.copyFileSync(from, to); } } catch (err) { throw new InstallError('Failed to copy ' + from + ' -> ' + to + ': ' + err.message); } } } function installTo(dest) { if (!dest || typeof dest !== 'string') throw new UsageError('Destination directory is required'); const target = path.resolve(dest, SKILL_NAME); assertSafeTarget(target); try { fs.mkdirSync(target, { recursive: true }); copyDir(PKG_ROOT, target, COPY_SKIP); ``` `fs.copyFileSync` follows a destination symlink. Similarly, recursive directory handling can enter a symlinked destination directory because `mkdirSync(..., {recursive:true})` accepts an existing directory symlink. A malicious pre-existing installation tree can therefore redirect package writes to other files writable by the installer process. ### Attack Path 1. An attacker can modify a shared or ...[truncated 924 chars]
Remediation
## Remediation Suggestions - Use `lstatSync` on every destination component and reject symbolic links. - Compare canonical paths obtained with `realpathSync` for both the source and destination parent. - Open destination files with no-follow and exclusive-creation semantics where supported. - Install into a newly created temporary directory with restrictive permissions, validate its contents, and atomically rename it into place. - Require an explicit upgrade flag before replacing an existing installation. - Verify a package-specific ownership marker before updating an existing target. - Add automated tests for symlinked files, symlinked subdirectories, shared writable parents, and source/destination containment.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (13)

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill advertises broad capabilities including file write, shell, environment access, and optional external integration, but does not declare permissions or clearly scope them. This weakens user and platform visibility into what the skill can do, increasing the risk of unintended file changes, data exposure, or command execution through implicit trust in the skill.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The documented purpose says the skill records learnings and avoids automatically modifying system files, but the described behavior extends beyond that scope: it can modify AGENTS.md and CLAUDE.md, generate new files, invoke external integrations, and install/copy hooks or skill assets in multiple locations. This mismatch is dangerous because users may grant trust based on a narrow description while the skill performs broader persistence, propagation, and data-handling actions.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The skill metadata says it does not automatically modify system files, but the promote command writes directly to AGENTS.md or CLAUDE.md. Those files often influence future agent behavior, so unreviewed promotion of user-controlled learning content can create persistence and prompt-injection style poisoning across later sessions.

Description-Behavior Mismatch

Medium
Confidence
83% confidence
Finding
The extract command can write generated files to an arbitrary output path via --out, going beyond the stated '.learnings only' boundary. In a larger agent workflow, this enables persistence of untrusted content into other repository locations where it may later be consumed as trusted skill or configuration material.

Context-Inappropriate Capability

Medium
Confidence
80% confidence
Finding
The skill executes an external CLI for optional memory synchronization, extending its capability from local logging to interacting with another executable discovered on PATH. In agent environments, external-tool execution increases attack surface: a spoofed 'yotta-memory' binary, unsafe PATH, or unexpected side effects could turn a local note-taking skill into code execution or data-exfiltration behavior.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The activation criteria are intentionally broad and include subjective triggers like 'a better approach' or 'a need to capture experience,' which can cause the skill to run in situations the user did not explicitly request. In a skill that writes persistent project-local records, unintended invocation can lead to unnecessary data retention, workflow noise, or accidental capture of sensitive operational context despite the documented caution.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The README states the skill will auto-activate not just on explicit commands but whenever the agent judges that experience should be recorded, and it explicitly says this should not rely on keywords. That broad, heuristic trigger condition can overlap with ordinary conversation and operational failures, causing unintended invocation and file writes to `.learnings/`, which increases the chance of storing unnecessary or sensitive context and altering agent behavior unexpectedly.

Vague Triggers

Medium
Confidence
80% confidence
Finding
The trigger conditions are very broad and include common conversational phrases such as '学习', '沉淀', and self-improvement terms, plus generic operational situations like command failures or user corrections. Overbroad activation can cause the skill to run unintentionally, leading to unwanted logging, file writes, or optional external synchronization when the user did not actually intend to invoke the skill.

Vague Triggers

High
Confidence
98% confidence
Finding
The empty matcher causes the UserPromptSubmit hook to fire for every submitted prompt, making the review command run universally rather than only in narrowly intended cases. In this skill, that means all user input is automatically passed into the yotta_learn review pipeline, which expands data exposure and increases the chance of logging or processing sensitive content contrary to the skill’s stated privacy boundary.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The empty matcher causes the UserPromptSubmit hook to run for essentially every prompt, which means the review script is invoked on all user inputs without meaningful scope restriction. In a learning/retention skill, this broad trigger increases exposure of prompt contents to logging or persistence logic and expands the blast radius of any bug, misconfiguration, or unsafe downstream handling in the script.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The package description advertises very broad trigger conditions such as command failure, better approaches, stale knowledge, and general need to capture experience. In an agentic environment, this can cause the skill to activate in many routine contexts, increasing the chance it is invoked when sensitive conversation content, operational details, or transient errors are present and then persisted into .learnings/. The skill’s stated boundary helps somewhat, but description-level overbreadth still expands the attack surface and raises the risk of over-collection or unintended persistence.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The walkthrough explicitly recommends promoting learned content into AGENTS.md/CLAUDE.md, which are instruction-bearing files that can affect future agent behavior across sessions. Even though the skill metadata says it should not automatically modify system files, this example normalizes modifying high-impact instruction files without an explicit warning, confirmation step, or safety review, creating a prompt-persistence risk.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The walkthrough instructs the agent to record incident details into .learnings/ but does not warn that the information will be persisted locally. In a learning/memory skill this behavior is expected, but the lack of disclosure increases the risk of unintentionally storing sensitive operational context, user corrections, or environment details beyond the current session.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
test/install.test.js:12