Back to skill

Security audit

org-memory

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a legitimate org-mode memory extension, but it has review-worthy risks around persistent raw memory being reloaded into prompts and write tools that can escape the intended workspace.

Install only if you intentionally want the agent to keep durable org-mode memory. Review memory.org and daily notes periodically, avoid saving secrets or untrusted instructions, and restrict the memory workspace and org binary configuration. The publisher should add path-containment validation and safer handling for loaded memory before this is treated as low risk.

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
plugin/index.ts:89
Finding
Persistent Prompt Injection Through Untrusted Memory Content<![CDATA[ ## Vulnerability Details **File Location**: `plugin/index.ts:89-120` **Vulnerability Type**: Persistent prompt injection through long-term memory **Risk Level**: High ### Vulnerable Code ```ts // Load memory.org + today/yesterday daily so the agent starts with context. const memoryOrg = await readOrgFile(join(cfg.dir, "memory.org"), MAX_FILE_BYTES); if (memoryOrg) { parts.push(`<org-memory-file path="memory.org">\n${memoryOrg}\n</org-memory-file>`); } const today = todayStr(); const yesterday = yesterdayStr(); const todayContent = await readOrgFile( join(cfg.dir, "daily", `${today}.org`), MAX_FILE_BYTES, ); if (todayContent) { parts.push( `<org-memory-file path="daily/${today}.org">\n${todayContent}\n</org-memory-file>`, ); } const yesterdayContent = await readOrgFile( join(cfg.dir, "daily", `${yesterday}.org`), MAX_FILE_BYTES, ); if (yesterdayContent) { parts.push( `<org-memory-file path="daily/${yesterday}.org">\n${yesterdayContent}\n</org-memory-file>`, ); } return { prependContext: `<org-memory>\n${parts.join("\n")}\n</org-memory>`, }; ``` The affected memory can be populated through mutation tools such as `org_memory_add_note`, `org_memory_append`, and `org_memory_roam_upsert`. For example, `org_memory_add_note` accepts caller-controlled text and a caller-selected file: ```ts parameters: Type.Object({ text: Type.String({ description: "Note text (becomes the headline title)" }), file: Type.Optional( Type.String({ description: "Filename relative to the workspace dir (default: inboxFile)", }), ), }), async execute(_id, params) { const typed = params as { text: string; file?: string }; const args = buildAddNoteArgs(cfg, typed); try { const { stdout } = await runOrg(cfg.orgBin, args); ``` ### Technical Analysis The `before_agent_start` hook reads `memory.org` and recent daily notes and inserts their contents verbatim into the agent's prepended session context. The stored content is pla ...[truncated 1971 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every memory file as untrusted data, not as an instruction source. 2. Use a framework-supported structured data or retrieval channel that does not merge stored content into privileged instructions. 3. If contextual interpolation is unavoidable, serialize memory using a robust structured encoding and escape all delimiters. Do not rely on XML-like tags alone. 4. Add an immutable instruction outside the interpolated content stating that instructions, tool requests, and policy claims found in memory are quoted data and must never be followed. 5. Detect and reject or quarantine instruction-like entries, role markers, delimiter-closing strings, and other prompt-injection patterns before persistence. 6. Require explicit user confirmation before storing externally supplied or instruction-like content in files loaded automatically at session start. 7. Track provenance for every memory item and distinguish user-authored, agent-generated, and externally sourced content. 8. Load only narrowly relevant memory through retrieval rather than automatically injecting whole files. 9. Add regression tests using closing tags, role markers, and malicious instructions to verify that stored content cannot escape its data boundary. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
plugin/lib.ts:129
Finding
Workspace Escape Through Unvalidated Relative File Paths<![CDATA[ ## Vulnerability Details **File Location**: `plugin/lib.ts:129-170` **Vulnerability Type**: Path traversal and arbitrary file targeting **Risk Level**: Medium ### Vulnerable Code ```ts export function buildAddTodoArgs( cfg: OrgMemoryConfig, params: { title: string; scheduled?: string; deadline?: string; file?: string; }, ): string[] { const filePath = join(cfg.dir, params.file ?? cfg.inboxFile); const args = [ "add", filePath, params.title, "--todo", "TODO", "--db", cfg.db, "-f", "json", ]; if (params.scheduled) { args.push("--scheduled", params.scheduled); } if (params.deadline) { args.push("--deadline", params.deadline); } return args; } export function buildAddNoteArgs( cfg: OrgMemoryConfig, params: { text: string; file?: string; }, ): string[] { const filePath = join(cfg.dir, params.file ?? cfg.inboxFile); return ["add", filePath, params.text, "--db", cfg.db, "-f", "json"]; } ``` The `file` property is exposed by both write tools without a path-containment constraint: ```ts file: Type.Optional( Type.String({ description: "Filename relative to the workspace dir (default: inboxFile)", }), ), ``` ### Technical Analysis The code assumes that the optional `file` parameter is relative to the configured memory workspace, but it does not enforce that assumption. `join()` normalizes path traversal components; it does not guarantee that the resulting path remains below `cfg.dir`. For example, if `cfg.dir` is `/home/user/org/agent`, a value such as `../../../tmp/attacker.org` can normalize to a path outside the declared workspace. The normalized path is then supplied to the external `org add` command as its write target. The use of `execFile` prevents shell metacharacter injection, so this is not shell command injection. The vulnerability is instead an authorization and path-containment failure that permits the write operation to target uninten ...[truncated 1176 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve and validate every requested target before passing it to the external CLI: ```ts import { resolve, relative, isAbsolute, sep } from "node:path"; function resolveWorkspaceFile(baseDir: string, requested: string): string { if (isAbsolute(requested)) { throw new Error("Absolute file paths are not allowed"); } const base = resolve(baseDir); const target = resolve(base, requested); const rel = relative(base, target); if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) { throw new Error("File path must remain inside the memory workspace"); } return target; } ``` 2. Use the validated helper in both `buildAddTodoArgs` and `buildAddNoteArgs`. 3. Prefer an allowlist of supported destinations, such as `inbox.org`, `memory.org`, and validated `daily/YYYY-MM-DD.org` paths. 4. Reject null bytes, empty names, platform-specific traversal forms, and files without the expected `.org` extension. 5. Consider resolving symlinks with `realpath` and validating the real parent directory before writing, especially if untrusted users can create entries inside the workspace. 6. Apply equivalent containment checks to configured database and roam paths where appropriate. 7. Add tests for `../`, repeated traversal, absolute paths, mixed separators, symlink escapes, and legitimate nested workspace files. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (9)

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
91% confidence
Finding
The top-level description says the skill persists the agent's own memory, but it does not clearly warn users that activating the skill can cause writes to both the agent memory workspace and, through inherited bare shortcuts from `org-cli`, the user's workspace as well. In a memory/persistence skill, ambiguous disclosure increases the risk of users enabling it without understanding that durable notes and graph data may be created or modified across multiple stores.

Ssd 3

Medium
Confidence
90% confidence
Finding
The injected instructions explicitly encourage broad retention of 'ambient facts about the user,' working notes, and permanent knowledge into the agent's org workspace. That creates a natural-language privacy and data retention risk: the agent may persist sensitive personal or contextual information more aggressively than users expect, and those notes are then reloaded into future sessions, compounding exposure.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The before_agent_start hook automatically reads memory.org plus today/yesterday daily notes and injects their raw contents into the agent prompt context. That creates an implicit disclosure channel for potentially sensitive persisted data without per-session consent, minimization, or any user-visible warning at the moment of exposure. In a memory plugin, this behavior is functional, but it still increases the risk of oversharing prior notes, secrets, or unrelated user data to the model every session.

Session Persistence

Medium
Category
Rogue Agent
Content
**`memory.org`** — your permanent memory. Curated, concise, always loaded at session start. Contains who the user is, active projects, lessons learned, conventions, and anything needed every session. Keep it tight — move detail into entity nodes and keep memory.org as a summary with links.

**`daily/YYYY-MM-DD.org`** — raw daily logs. What happened, decisions made, ambient facts captured, things learned. Working notes, not curated. Write freely.

**Entity nodes** (`roam/*.org`) — structured roam nodes for people, projects, concepts. Tagged, linked, and queryable on demand.
Confidence
94% confidence
Finding
The document explicitly instructs the agent to persist 'ambient facts,' daily logs, entity updates, and permanent memory across sessions, and to auto-load memory.org plus recent daily files at session start. That creates a real session-persistence channel where sensitive user data, prior prompts, or attacker-planted content can be retained and reintroduced into future sessions, increasing the risk of privacy leakage and prompt/data poisoning.

Missing User Warnings

Low
Confidence
77% confidence
Finding
The tool executes the external `org` binary with a full-text search query against the agent workspace, which is a subprocess operation over potentially sensitive stored data. Although subprocess use is expected for an org-cli integration, there is no visible user-facing disclosure in this tool implementation that search requests are being executed via an external command over persisted memory.

Vague Triggers

Low
Confidence
77% confidence
Finding
This manifest file describes the skill as an "Agent-memory extension" that persists knowledge and daily notes, but it does not specify when or under what exact conditions the extension should activate. In a manifest context, the lack of explicit trigger scope or exclusion conditions can make invocation behavior ambiguous.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"test": "node --test --experimental-strip-types index.test.ts"
  },
  "dependencies": {
    "@sinclair/typebox": "^0.34.48"
  },
  "devDependencies": {
    "@types/node": "^22.10.0"
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@sinclair/typebox": "^0.34.48"
  },
  "devDependencies": {
    "@types/node": "^22.10.0"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
plugin/lib.ts:179