Back to skill

Security audit

Active Learner

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated purpose, but it can persist arbitrary lesson text into agent memory and has an undocumented file override that can modify other writable files.

Install only if you are comfortable with a skill that can rewrite persistent agent memory. Use it only with trusted lesson text, review MEMORY.md after use, and avoid exposing the command to untrusted automation or inputs. The publisher should document or remove --file, restrict writes to the intended memory file, and add approval/provenance checks before persistence.

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

T02 · Agent Memory Poisoning

Error
Location
index.js:28
Finding
Persistent Agent Memory Poisoning Through Untrusted Lesson Content<![CDATA[ ## Vulnerability Details **File Location**: `index.js:28`, `index.js:58-62`, `index.js:74-83`, and `index.js:123-128` **Vulnerability Type**: Persistent storage of untrusted instructions **Risk Level**: High ### Vulnerable Code ```js const MEMORY_FILE = ARGS.file ? path.resolve(ARGS.file) : path.resolve(__dirname, '../../MEMORY.md'); ``` ```js function internalize(text, category, id) { if (!text || !category) { console.error("Error: --text and --category are required for 'internalize'."); process.exit(1); } if (!fs.existsSync(MEMORY_FILE)) { console.error("Error: MEMORY.md not found."); process.exit(1); } ``` ```js let content = fs.readFileSync(MEMORY_FILE, 'utf8'); // Check if ID already exists if (content.includes(`| **${id}** |`)) { console.log(`Entry ${id} already exists in MEMORY.md. Skipping.`); return; } // 1. Prepare Table Row // | ID | Type | Category | Summary | ~Tok | const tokens = Math.ceil(text.length / 4); // Extract first sentence or first 50 chars for summary const summaryLine = text.split('\n')[0].replace(/[|]/g, '-'); // Escape pipes const summary = summaryLine.substring(0, 50) + (summaryLine.length > 50 ? '...' : ''); const type = "Lesson"; ``` ```js const detailEntry = `\n### ${id} | ${category} | ${summary}\n**Date:** ${new Date().toISOString().split('T')[0]}\n${text}\n`; content += detailEntry; fs.writeFileSync(MEMORY_FILE, content); console.log(`Successfully internalized ${id} to MEMORY.md (Index + Detail)`); ``` ### Technical Analysis The `internalize` command treats the caller-controlled `text`, `category`, and `id` arguments as trusted memory content and stores them in `MEMORY.md`. The implementation does not distinguish factual lesson data from executable natural-language instructions. Only pipe characters in the first-line summary are replaced. The complete `text` value is appended without sanitization, and `category` and `id` are interpolated into Markdown headings and table content ...[truncated 1906 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require explicit human confirmation before persisting any lesson originating from an external or untrusted source. 2. Store lessons as structured records with provenance, author identity, trust level, creation time, and approval state. 3. Treat persisted lesson text as quoted data, never as authoritative instructions. Ensure consuming agents are explicitly instructed not to execute directives embedded in memory entries. 4. Validate `id` and `category` against restrictive allowlists, such as: - `id`: `^[A-Z][0-9]+$` - `category`: a fixed set of supported categories 5. Escape Markdown metacharacters and prevent user input from creating headings, tables, comments, or other structural content. 6. Detect and quarantine instruction-like content, including requests to ignore prior rules, expose secrets, invoke tools, or modify security policy. 7. Separate untrusted observations from trusted policy memory. Untrusted records should require review before promotion into long-term trusted memory. 8. Add tests covering multiline prompt injection, forged headings, Markdown structure injection, and attempts to redefine agent behavior. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:28
Finding
Arbitrary Existing File Modification Through Unrestricted --file Path<![CDATA[ ## Vulnerability Details **File Location**: `index.js:28`, `index.js:58-62`, `index.js:74`, and `index.js:123-128` **Vulnerability Type**: Unrestricted file path used for read-modify-write operations **Risk Level**: Medium ### Vulnerable Code ```js const MEMORY_FILE = ARGS.file ? path.resolve(ARGS.file) : path.resolve(__dirname, '../../MEMORY.md'); ``` ```js function internalize(text, category, id) { if (!text || !category) { console.error("Error: --text and --category are required for 'internalize'."); process.exit(1); } if (!fs.existsSync(MEMORY_FILE)) { console.error("Error: MEMORY.md not found."); process.exit(1); } ``` ```js let content = fs.readFileSync(MEMORY_FILE, 'utf8'); ``` ```js const detailEntry = `\n### ${id} | ${category} | ${summary}\n**Date:** ${new Date().toISOString().split('T')[0]}\n${text}\n`; content += detailEntry; fs.writeFileSync(MEMORY_FILE, content); console.log(`Successfully internalized ${id} to MEMORY.md (Index + Detail)`); ``` ### Technical Analysis The argument parser accepts any option beginning with `--`, including the undocumented `--file` option used at line 28. Its value is passed to `path.resolve()` without checking whether the resulting path is the intended `MEMORY.md` file or is located beneath an approved workspace directory. When the `internalize` command runs, the target only needs to exist. The program reads the entire target as UTF-8 text, inserts or appends attacker-controlled Markdown, and rewrites it with `fs.writeFileSync()`. There is no canonical-path containment check, expected-file validation, symlink rejection, file-type validation, or confirmation prompt. This creates an arbitrary existing-file modification primitive within the permissions of the Node.js process. It does not independently bypass operating-system access controls, but it can modify any existing file writable by the invoking account. ### Attack Path 1. An attacker can invoke the skill or influence a ...[truncated 1490 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove support for `--file` if selecting arbitrary memory files is not a documented requirement. 2. If configurable paths are necessary, restrict targets to a fixed approved directory: - Resolve the approved root with `fs.realpathSync()`. - Resolve the target's canonical path. - Confirm that the target remains beneath the approved root using `path.relative()`. - Reject absolute paths and parent-directory traversal where possible. 3. Reject symbolic links using `lstatSync()` and use operating-system no-follow protections where supported. 4. Require the target filename to match an expected name such as `MEMORY.md`. 5. Validate that the target contains the expected memory-file structure before modifying it. Do not append to arbitrary content as a fallback. 6. Perform atomic updates by writing to a securely created temporary file in the same directory, setting restrictive permissions, syncing it, and renaming it over the validated target. 7. Avoid running the skill with elevated privileges and grant write access only to the intended memory file. 8. Document every supported option and reject unknown command-line arguments so hidden or accidental capabilities cannot be invoked. 9. Add tests for absolute paths, `../` traversal, symlink targets, malformed files, files outside the workspace, and targets with unsafe permissions. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (2)

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill description explicitly says it can programmatically internalize lessons into MEMORY.md, but it does not warn that this alters persistent agent memory. That omission can cause users or higher-level orchestration to invoke the skill without understanding it performs state-changing writes, increasing the risk of memory poisoning, persistence of bad instructions, or accidental storage of untrusted content.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"start": "node index.js"
  },
  "dependencies": {
    "minimist": "^1.2.8"
  }
}
Confidence
93% confidence
Finding
The dependency uses a caret range (^1.2.8), which allows automatic installation of newer compatible versions rather than a single fixed version. This increases supply-chain risk and can reduce build reproducibility if a later published version introduces malicious code or a breaking security regression.