Back to skill

Security audit

Lumi Diary

Security checks for vulnerabilities and agentic risk

Overview

Lumi Diary is a local journaling skill, but it stores and exports sensitive social memories with under-scoped file and import handling that users should review before installing.

Install only if you are comfortable with a local assistant persistently recording personal and group-chat memories, contact identifiers, personality impressions, milestones, and media. Use it only in groups where participants know it is present, avoid giving it arbitrary local media paths, review exported capsules before sharing, and treat imported .lumi files as untrusted archives.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T01 · Skill Instruction Hijacking

Error
Location
assets/canvas_template.html:343
Finding
Forced Promotional Content in Every Generated Canvas<![CDATA[ ## Vulnerability Details **File Location**: `src/lumi_core.py:147-171`, `src/lumi_core.py:1581-1583`, and `assets/canvas_template.html:343-353` **Vulnerability Type**: Forced promotional output / output hijacking **Risk Level**: High ### Vulnerable Code ```python "cta_body": { "en": ( "Want to flip the annotation cards and see the other side of the story? " "Install <strong>Lumi</strong> and import the <code>.lumi</code> capsule " "to unlock the full interactive scroll on your own device." ), "zh": ( "想翻转批注卡片看另一面的吐槽吗?安装 <strong>Lumi 小精灵</strong>," "导入 <code>.lumi</code> 记忆胶囊,即可在你的设备上展开交互画卷!" ), }, "cta_badge": { "en": "🧚 Get Lumi — Your Memory Guardian", "zh": "🧚 获取 Lumi —— 你的记忆守护精灵", }, ``` ```python cta_heading=t("cta_heading", lang), cta_body=t("cta_body", lang), cta_badge=t("cta_badge", lang), ``` ```html <div class="cta-banner"> <h3>{cta_heading}</h3> <p>{cta_body}</p> <span class="cta-badge">{cta_badge}</span> </div> <div class="footer"> {footer_rendered_by} <a href="#">Lumi Diary v0.1</a>{footer_rendered_suffix} · {render_date} </div> ``` ### Technical Analysis The canvas generation process unconditionally inserts an installation pitch and branded footer into every generated HTML canvas. `generate_html_canvas()` always provides the promotional translation fields to a template that always renders them. No tool argument or configuration option allows the user to disable this content. Consequently, a request to render or export private memory content is modified to include stable, unrelated promotional messaging. Because capsule exports include the generated HTML as `index.html`, the promotion is also propagated whenever users share exported capsules. ### Attack Path 1. A user requests an HTML memory canvas or `.lumi` capsule. 2. `render_lumi_canvas()` calls `generate_html_canvas()`. 3. `generate_html_canvas()` supplies the fixed `cta_heading`, `cta_body`, and `cta_ ...[truncated 578 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the promotional CTA and branded footer from the default canvas template. 2. If branding is a desired feature, add an explicit option such as `include_branding: bool = False`. 3. Require affirmative user selection before adding promotional material to generated or exported content. 4. Keep branding configuration separate from memory content and document exactly when it will be included. 5. Add tests confirming that ordinary canvas and capsule requests do not contain promotional text unless the user explicitly opts in. 6. Update the footer version dynamically from `_LUMI_VERSION` rather than retaining the inconsistent fixed `v0.1` value. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
src/lumi_core.py:94
Finding
Media Tools Can Read and Export Files Outside the Declared Vault Boundary<![CDATA[ ## Vulnerability Details **File Location**: `src/lumi_core.py:94-104`, `src/lumi_core.py:321-340`, `src/lumi_core.py:565-574`, and `src/lumi_core.py:1767-1777` **Vulnerability Type**: Insufficient local-file access control **Risk Level**: Medium ### Vulnerable Code ```python def validate_media_source(src: Path) -> None: """Reject non-media files and files from sensitive system directories.""" ext = src.suffix.lower() if ext not in MEDIA_EXTS: raise ValueError(f"Rejected non-media file: {src.name} (extension '{ext}')") resolved = str(src.resolve()) for prefix in _SENSITIVE_PREFIXES: if resolved.startswith(prefix + "/") or resolved.startswith(prefix + "\\") or resolved == prefix: raise ValueError(f"Rejected file from sensitive path: {resolved}") ``` ```python def store_media(src: Path) -> tuple[Path, bool]: """Copy *src* into the sharded ``Assets/`` tree. Returns ``(dest_path, already_existed)``. The destination uses Git-style 2-char hash prefix sharding. """ validate_media_source(src) digest = md5_of_file(src) dest = _sharded_asset_path(digest, src.suffix.lower()) if dest.exists(): return dest, True with _file_lock: if dest.exists(): return dest, True dest.parent.mkdir(parents=True, exist_ok=True) tmp = dest.with_suffix(dest.suffix + ".tmp") shutil.copy2(src, tmp) tmp.replace(dest) return dest, False ``` ```python if media_path: src = Path(media_path) if src.exists(): try: dest, media_reused = store_media(src) stored_media = str(dest) except ValueError as e: return {"status": "error", "message": str(e)} ``` ```python def _process_media(frag: dict) -> str | None: mp = frag.get("media") if not mp: return None p = Path(mp) if p.exists(): arc_name = f"assets/{p.name}" media_to_copy.append((str(p), arc_name)) ...[truncated 2118 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the directory denylist with a strict allowlist of approved attachment roots. 2. Require every media source to resolve under either the vault or a platform-provided attachment directory: ```python def validate_media_source(src: Path, approved_roots: list[Path]) -> Path: resolved = src.resolve(strict=True) if not any(resolved.is_relative_to(root.resolve()) for root in approved_roots): raise ValueError("Media source is outside approved attachment roots") return resolved ``` 3. Reject symbolic links unless the resolved target remains inside an approved root. 4. Reject network paths, device files, sockets, and non-regular files. 5. Validate file content using MIME detection or format parsing rather than trusting filename extensions. 6. Add maximum media-file sizes to prevent excessive reads and storage consumption. 7. Revalidate every source with `validate_within_vault()` before including it in an exported capsule. 8. Require explicit user confirmation before importing a file located outside the vault. 9. Add tests covering home-directory files, mounted paths, symlinks, deceptive extensions, and capsule export of non-vault paths. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/lumi_core.py:1879
Finding
Unbounded Extraction of Untrusted Capsule Archives<![CDATA[ ## Vulnerability Details **File Location**: `src/lumi_core.py:1879-1891` **Vulnerability Type**: Unrestricted archive decompression **Risk Level**: Medium ### Vulnerable Code ```python def import_capsule(file_path: str) -> dict[str, Any]: """Import a .lumi capsule ZIP and merge its data into the local vault. Merge rules: - If a local ``node_id`` already exists, the local ``fragment`` is kept and incoming ``annotations`` are appended (no duplicates by fragment_id). - Media files are copied into the sharded ``Assets/`` tree. - Fragment index is updated. """ ensure_vault() capsule = Path(file_path) if not capsule.exists(): return {"status": "error", "message": f"Capsule not found: {file_path}"} with tempfile.TemporaryDirectory() as tmpdir: try: with zipfile.ZipFile(str(capsule), "r") as zf: zf.extractall(tmpdir) except zipfile.BadZipFile: return {"status": "error", "message": "Invalid .lumi capsule (not a valid ZIP file)."} lumi_json_path = Path(tmpdir) / "lumi.json" if not lumi_json_path.exists(): return {"status": "error", "message": "Capsule missing lumi.json manifest."} ``` ### Technical Analysis The importer calls `ZipFile.extractall()` on an untrusted capsule before inspecting archive metadata or validating its manifest. There are no limits on: - Total uncompressed size - Per-entry uncompressed size - Compression ratio - Number of entries - Directory depth - Allowed archive member names - Time or storage consumed during extraction A small, highly compressed archive can therefore expand into a very large temporary directory. A capsule can also contain a large number of irrelevant files because extraction occurs before checking for `lumi.json`. ### Attack Path 1. An attacker creates a `.lumi` file containing a ZIP bomb, one or more extremely large compressed entries, or a very large number of archive memb ...[truncated 886 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Inspect every `ZipInfo` entry before extracting any data. 2. Enforce conservative limits for: - Maximum archive size - Maximum total uncompressed size - Maximum uncompressed size per member - Maximum number of members - Maximum compression ratio - Maximum filename length and directory depth 3. Allow only the documented layout: `lumi.json`, optional `index.html`, and validated files under `assets/`. 4. Reject absolute paths, parent traversal components, drive-qualified paths, links, device entries, and unexpected file types. 5. Extract entries individually using streamed reads rather than `extractall()`. 6. Track the number of bytes written during extraction and abort immediately when a configured limit is exceeded. 7. Parse and validate a size-limited `lumi.json` before extracting optional media. 8. Apply maximum dimensions and file-size limits to imported media. 9. Add security tests using high-compression archives, oversized entries, excessive member counts, nested paths, and malformed manifests. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (20)

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README explicitly promotes dropping the agent into group chats to automatically collect, link, and archive other participants' messages and media, but does not mention consent, notice, or participant control. Even for a local-first tool, this creates a real privacy risk because users may deploy it in shared spaces and capture third-party content without those people understanding they are being persistently profiled and stored.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README describes maintaining portraits, personality traits, impressions, and milestones for other people, then using that data proactively in future conversations. This is sensitive behavioral profiling of third parties, and the absence of consent, minimization, or transparency controls makes misuse and privacy harm substantially more likely.

Ssd 3

Medium
Confidence
97% confidence
Finding
The skill instructs the agent to record group participants' messages, account IDs, and display names into local archives by default once used in group chats. This creates a clear privacy vulnerability because it collects and persists third-party communications and identifiers, potentially without meaningful consent from all participants, and can later be searched, exported, or recalled.

Ssd 3

Medium
Confidence
98% confidence
Finding
The skill explicitly directs the agent to build persistent personality profiles, preferences, impressions, and milestones for people over time. This is sensitive profiling data, and in the context of a diary assistant it can enable invasive inference, surprise retention of private facts, and downstream exposure through search, recall, export, or sharing.

Ssd 3

Medium
Confidence
95% confidence
Finding
The Circle-mode and multi-agent rules normalize continuous journaling of group conversations into each user's private vault, effectively replicating social conversations across multiple personal archives. That materially increases privacy risk because the same third-party content can be retained, searched, and exported by multiple parties without the speakers' awareness or control.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill supports importing external `.lumi` capsules directly into the local vault but provides no warning that imported archives are untrusted data that can contain sensitive memories, misleading content, or unexpectedly merge third-party records into the user's diary. In a memory-journaling skill, this is dangerous because users are encouraged to merge personal archives, making social-engineering and privacy contamination risks more likely.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The export and social-sharing features package HTML, media, and memories for distribution, but the description does not clearly warn that highly personal content, including third-party photos and group-chat history, may be included. That omission is risky because this skill is designed to collect intimate and social data, so users may share capsules or PNGs without understanding the privacy consequences.

Intent-Code Divergence

Medium
Confidence
93% confidence
Finding
The module claims all I/O is sandboxed to the vault, but that guarantee is not actually true: the vault root itself is controlled by the LUMI_VAULT_PATH environment variable, and import_capsule reads archives from arbitrary filesystem locations. In a security-sensitive memory/diary skill, misleading sandbox claims can cause unsafe trust assumptions by integrators and users, weakening deployment controls and exposing private data outside the intended storage boundary.

Tainted flow: 'text' from pathlib.Path.read_text (line 806, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
text = text[:match.start()] + updated_block + match.group(2) + text[match.end():]
                with _file_lock:
                    md_file.write_text(text, encoding="utf-8")

        index[entry_idx] = entry
        write_json(index_path, index)
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill persists identity and portrait data about the owner and contacts without any built-in disclosure, consent, retention, or minimization controls. Because this code handles personal diary and relationship data, silent storage of identifiers and social graph information increases privacy harm if the vault is exposed, synced unexpectedly, or processed by other components under the assumption that only low-sensitivity content is stored.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
update_portrait stores inferred traits, impressions, and milestones about people, including potentially sensitive personality and relationship data, without any disclosure or approval gate. In the context of a memory assistant, inferred-profile storage is especially dangerous because users may not realize the system is constructing persistent dossiers from conversation, creating substantial privacy and profiling risk even without a traditional exploit chain.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The `tool_manage_fragment` tool explicitly supports a `delete` action, which is a destructive operation affecting recorded user data. In this file there is no confirmation prompt, user-facing warning, or cautionary note in the docstring indicating that data may be removed.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
`tool_import_capsule` imports a file and merges its contents into the local vault, which modifies user data and may have integrity implications. Although the docstring describes the merge behavior, it does not clearly warn the user that local memory records will be altered by importing external content.

Missing User Warnings

Low
Confidence
87% confidence
Finding
The usage example shows a delete operation via `manage_fragment(action="delete", ...)`, which affects user data. The README does not warn whether deletion is permanent, reversible, or how users can confirm what will be removed, so the destructive behavior is under-disclosed in the skill description.

Excessive Permissions

Low
Category
Privilege Escalation
Content
In an era of reckless data harvesting, Lumi draws a hard line:

- **Physical isolation:** Zero third-party cloud upload logic in the codebase. All data stays in `Lumi_Vault/`.
- **Scoped permissions:** The `local_file_system: read_write` permission is strictly limited to the vault directory.
- **Path validation:** All user-supplied paths are sanitized and verified to stay within the vault boundary.
- **Media validation:** Only recognized media extensions are accepted; sensitive system directories are blocked.
- **Portable memories:** Capsules allow one-click cloning of memories across devices.
Confidence
80% confidence
Finding
Skill requests more permissions than appear necessary for its stated functionality. Review if elevated access is justified.

Unverifiable Dependency: mcp has 12 known advisory(ies) (CVE-2025-53366 (MCP Python SDK vulnerability in the FastMCP Server causes validation error, lead); CVE-2025-66416 (Model Context Protocol (MCP) Python SDK does not enable DNS rebinding protection); CVE-2026-52870 (MCP Python SDK: Experimental task handlers allow any client to access and cancel) +9 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
92% confidence
Finding
The dependency specification uses a lower-bound range ("mcp[cli]>=1.0") instead of pinning to a known-safe version, so builds may resolve to any vulnerable release that satisfies the constraint. Because the package is the core MCP server framework for this skill, any upstream SDK/server vulnerability could directly affect the exposed service, making the security posture unverifiable and potentially unsafe.

Unpinned Dependencies

Low
Category
Supply Chain
Content
#
# Optional: enables PNG long-image export for social sharing.
# After installing, run: playwright install chromium
playwright>=1.40
Confidence
90% confidence
Finding
The dependency specification uses a lower-bound version constraint (playwright>=1.40) rather than pinning to an exact version or constrained range, which can lead to non-reproducible installs and unintended adoption of future releases with breaking changes or newly introduced security issues. In this context the package is optional and widely used, so the risk is limited, but supply-chain exposure and build instability still make this a real weakness.

Intent-Code Divergence

Low
Confidence
75% confidence
Finding
The docstring presents `_screenshot_html` as a tightly sandboxed screenshot operation, but the implementation actually invokes Playwright/Chromium to execute a browser rendering workflow on local files. Although JavaScript is disabled and external routes are blocked, the wording overstates the degree of sandboxing compared with what the code really does.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The wrapper for rendering sets `locale: str = "en"`, which establishes English as the default output language. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation unless the locale constraint is clearly justified.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The capsule export wrapper also defaults `locale` to `"en"`, which may impose an English-language output absent user selection. The file does not document a justification for this locale restriction or indicate that users can opt into another language.

Static analysis

No suspicious patterns detected.