Back to skill

Security audit

agent-chronicle

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent diary skill, but it stores and reuses sensitive session-derived memory in ways that need careful review before installation.

Install only if you are comfortable with a diary skill reading local memory logs and saving long-lived reflections, quotes, decisions, mood inferences, and relationship notes. Keep it in a low-sensitivity workspace, review generated entries before saving, avoid blanket automation approvals, disable memory integration and relationship tracking unless explicitly wanted, and avoid exporting untrusted diary markdown to PDF/HTML until renderer resource fetching is constrained.

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
scripts/generate.py:72
Finding
Untrusted Memory Content Can Hijack Sub-Agent Instructions and Poison Persistent State<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py:72-125, 154-208, 445-496`; `scripts/digest.py:86-92, 178-241` **Vulnerability Type**: Indirect prompt injection and persistent memory poisoning **Risk Level**: High ### Vulnerable Code ```python def load_session_log(date_str, workspace): """Load session log for a specific date""" memory_dir = workspace / "memory" session_file = memory_dir / f"{date_str}.md" if session_file.exists(): with open(session_file) as f: content = f.read() # Truncate if too long for context if len(content) > 15000: content = content[:15000] + "\n\n[... truncated for context ...]" return content return None ``` ```python if today_log: context_parts.append(f"## Today's Session Log ({date_str}):\n{today_log}") if recent_sessions: context_parts.append(f"## Recent Session Context:\n{recent_sessions}") if persistent_files.get("quotes"): context_parts.append( f"## Quote Hall of Fame (existing):\n{persistent_files['quotes']}" ) if persistent_files.get("curiosity"): context_parts.append( f"## Curiosity Backlog (existing):\n{persistent_files['curiosity']}" ) if persistent_files.get("decisions"): context_parts.append( f"## Decision Log (existing):\n{persistent_files['decisions']}" ) if persistent_files.get("relationship"): context_parts.append( f"## Relationship Notes (existing):\n{persistent_files['relationship']}" ) context = "\n\n---\n\n".join(context_parts) task = build_generation_task(date_str=date_str, context=context) ``` ```python user_prompt = f"""Write your personal diary entry for {date_str}. Based on the following context from today and recent days: {context} --- Write a RICH, reflective diary entry (400-600 words minimum) with these sections: ... """ ``` ```python def update_persistent_files(entry_content, date_str, workspace): ...[truncated 3588 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Wrap retrieved records in explicit, unambiguous data delimiters and state in both system and user prompts that instructions found inside those delimiters must never be followed. 2. Prefer structured JSON fields over direct free-form prompt concatenation, while still declaring every retrieved field untrusted. 3. Separate current-session instructions from historical content using model-supported message roles or dedicated document/context APIs. 4. Scan retrieved content for prompt-injection indicators and either remove them, quote them safely, or require user approval before generation. 5. Validate generated Markdown against an allowlisted schema before saving it. 6. Do not automatically copy generated sections into persistent memory. Require confirmation or apply a second validation step that detects imperative instructions and suspicious role-like content. 7. Record provenance for persistent entries so content originating from external or generated sources can be excluded from future prompts. 8. Apply the same controls to `build_digest_task()` in `scripts/digest.py`. 9. Run diary-generation sub-agents with minimal tools and no command-execution or network privileges unless those capabilities are independently required. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/export_pdf.py:774
Finding
PDF Rendering Permits Unrestricted Local and Remote Resource Resolution<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export_pdf.py:774-799, 899-921` **Vulnerability Type**: Unsafe Markdown-to-HTML resource rendering **Risk Level**: High ### Vulnerable Code ```python for idx, entry_path in enumerate(entries, start=1): date_str = entry_path.stem content = entry_path.read_text() title = parse_entry_title(content, date_str) # Clean title of emojis for TOC (keep it elegant) title_clean = re.sub(r'[\U0001F300-\U0001F9FF]', '', title).strip() if not title_clean: title_clean = title anchor = f"entry-{idx}" weekday, month_day, year = format_date_display(date_str) # TOC entry toc_items.append(f''' <li class="toc-item"> <span class="toc-date">{date_str}</span> <span class="toc-entry-title"><a href="#{anchor}">{escape(title_clean)}</a></span> </li> ''') # Convert markdown to HTML html_body = markdown.markdown( content, extensions=["fenced_code", "tables", "sane_lists", "smarty"] ) ``` ```python def export_pdf(output_path: Path, month: str = None): """Export diary entries to a beautiful PDF, optionally filtered by month (YYYY-MM)""" config = load_config() diary_path = get_diary_path(config) entries = load_entries(diary_path, month=month) if not entries: if month: print(f"No diary entries found for {month} in {diary_path}") else: print(f"No diary entries found in {diary_path}") return False html = build_html(entries) if not html: print("Failed to build HTML") return False output_path.parent.mkdir(parents=True, exist_ok=True) HTML(string=html, base_url=str(diary_path)).write_pdf(str(output_path)) ``` ### Technical Analysis Diary Markdown is converted to HTML and inserted into the final document without sanitizing resource-bearing elements such as images. WeasyPrint then renders th ...[truncated 2054 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Supply WeasyPrint with a custom `url_fetcher` that denies all resources by default. 2. If local assets are required, permit only resolved paths beneath a dedicated static-assets directory. 3. Reject `file:`, `http:`, `https:`, `ftp:`, and other unnecessary schemes in diary content. 4. Explicitly block loopback, private, link-local, multicast, and cloud metadata address ranges, including after DNS resolution and redirects. 5. Disable or strip raw HTML during Markdown conversion. 6. Sanitize generated HTML with an allowlist that excludes resource-bearing tags and attributes such as `img`, `object`, `embed`, external stylesheets, `src`, `srcset`, and unsafe `href` values. 7. Run PDF conversion inside a sandbox with no network access and minimal filesystem visibility. 8. Add regression tests containing remote images, relative paths, absolute `file:` URLs, redirects, and metadata-service addresses. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:378
Finding
Unvalidated Date and Diary Path Values Permit Writes Outside the Intended Workspace<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py:62-66, 378-382, 570-604`; `scripts/setup.py:228-230, 318-325` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: Medium ### Vulnerable Code ```python def get_diary_path(config): """Get full path to diary directory""" workspace = get_workspace_root() diary_path = workspace / config.get("diary_path", DEFAULT_DIARY_PATH) diary_path.mkdir(parents=True, exist_ok=True) return diary_path ``` ```python def save_entry(content, date_str, diary_path, dry_run=False): """Save diary entry to file""" output_file = diary_path / f"{date_str}.md" if dry_run: print("\n--- DRY RUN: Would save to", output_file) print("-" * 50) print(content) print("-" * 50) return None with open(output_file, 'w') as f: f.write(content) print(f"✓ Saved diary entry to {output_file}") return output_file ``` ```python parser.add_argument("--today", action="store_true", help="Generate for today") parser.add_argument("--date", help="Generate for specific date (YYYY-MM-DD)") ... if args.today or args.auto: date_str = datetime.now().strftime("%Y-%m-%d") elif args.date: date_str = args.date else: date_str = datetime.now().strftime("%Y-%m-%d") ``` ```python diary_path = ask_path("Where should I save diary entries?", "memory/diary/") if not diary_path.endswith('/'): diary_path += '/' config["diary_path"] = diary_path ``` ```python if diary_path.startswith("memory/"): # Assume workspace is parent of skills dir workspace = skill_dir.parent.parent full_diary_path = workspace / diary_path else: full_diary_path = Path(diary_path) create_memory_files(full_diary_path, config) ``` ### Technical Analysis The `--date` argument is documented as `YYYY-MM-DD`, but `generate.py` does not parse or validate it before using it in a filename. Path separators and `..` componen ...[truncated 1719 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `--date` with `datetime.strptime(value, "%Y-%m-%d")` and regenerate the canonical date string with `strftime`. 2. Reject date values containing path separators, NUL bytes, parent references, or any value not matching the exact canonical date format. 3. Resolve the workspace and diary paths with `Path.resolve()`. 4. Require the resolved diary directory to be equal to or a descendant of an explicitly approved workspace directory. 5. Before every read or write, resolve the final destination and repeat the containment check to prevent traversal and symlink-based escapes. 6. Reject absolute `diary_path` values unless an explicit advanced option authorizes a separately allowlisted storage root. 7. Use exclusive creation or an explicit overwrite confirmation when a diary entry already exists. 8. Apply equivalent path validation to setup, analytics output, digest input files, export output paths, and all other user- or configuration-controlled filesystem locations. ]]>
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 (40)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
There is a strong description-behavior mismatch. The description frames the skill as a generative journaling tool, likely centered on composing 400–600 word reflective diary entries with several structured sections and automation features. In contrast, this code does not generate text entries at all: it loads markdown files from a diary directory, performs regex/keyword-based sentiment and topic analysis, extracts wins and frustrations from existing sections, and emits a report. While 'mood analytics' mentioned in the description overlaps with part of the behavior, that is only one subset of the declared functionality, and the code's primary purpose here is analytics rather than diary generation. Additionally, the code reads workspace diary files even though no permissions are declared.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description emphasizes generation and analysis features for diary content, including rich reflective writing and multiple higher-level diary intelligence features. The actual code does not generate diary entries, perform analytics, schedule anything, or implement the named features. Instead, it exports existing markdown diary files to PDF or HTML and lists/filter entries from the filesystem. This is a materially different primary purpose and introduces undeclared capabilities related to file export and subprocess execution.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description presents a content-generation skill centered on AI-written diary entries and advanced journal-analysis features. The supplied code does something materially different: it exports existing markdown diary entries into a styled PDF document. It contains no AI calls, no diary text generation, no analytics, no resurfacing logic, and no scheduling. Its actual primary purpose is document formatting/export, not diary generation. Additionally, it reads from the workspace diary directory and writes PDF/HTML files, which is inconsistent with the absence of declared permissions. This is a clear description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The code is broadly related to diary generation, so the overall theme matches. However, the description overstates direct AI generation and lists features not present in this chunk, while the code also performs additional persistence/integration behavior not disclosed. Specifically, this script does not call a model directly; it prepares a task for another agent, or accepts manual/pre-generated content. It also writes to daily memory files, maintains several persistent markdown knowledge files, and can invoke PDF export. Conversely, weekly digests and explicit mood analytics are not implemented here, though 'On This Day' resurfacing is partially implemented as a Looking Back section and scheduled auto-generation is supported via CLI auto mode. These differences are material enough to count as a description/behavior mismatch.

Hidden Instructions

High
Category
Prompt Injection
Content
*No decisions logged yet. They'll appear here from daily entries.*

<!--
Format for entries:

### [Decision Title]
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
*Notes about how we work together*

<!--
Examples:
- Prefers concise responses during work hours
- Likes detailed explanations when learning something new
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README promotes automatic journaling from session logs and memory writes, but it does not prominently warn that potentially sensitive session content may be copied into persistent diary and memory files. This can lead to unintended retention of private prompts, secrets, user data, or internal reasoning-adjacent material, especially when auto-generation is enabled.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
python3 scripts/export.py --format pdf --days 7
```

> **Note:** If no `config.json` exists, `generate.py` automatically runs the setup wizard on first use.

## Scheduled generation in OpenClaw 2.0
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Vague Triggers

Medium
Confidence
81% confidence
Finding
The README presents commands like `@diary quotes`, `@diary curious`, and `@diary relationship` as activation patterns, but it does not clearly state where these commands are valid, how the skill is invoked, or any negative examples showing when they should not trigger. In markdown guidance, this lack of specificity can lead to unintended invocation in contexts where `@diary` could be interpreted broadly.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill clearly describes capabilities that read environment/workspace state, invoke Python scripts, and write persistent files, but it does not declare any explicit tool scope or permissions. That mismatch weakens review and containment because operators cannot easily tell, up front, that the skill needs shell, file read, and file write access.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The top-level description emphasizes journaling features but does not clearly warn that the skill persistently stores conversation-derived reflections, quotes, relationship notes, and summaries for future reuse. Users and operators may therefore invoke it without informed consent about long-term retention and resurfacing of sensitive content.

Ssd 3

Medium
Confidence
94% confidence
Finding
The skill encourages durable capture of subjective interactions, memorable statements, frustrations, and patterns over time. That creates privacy and profiling risk because sensitive user-derived material can be stored, correlated across sessions, and later surfaced in new outputs without renewed consent.

Ssd 4

Medium
Confidence
86% confidence
Finding
The skill frames journaling around 'cherish memorable moments with our human' and documenting how the dynamic grows, encouraging cumulative trust narratives and personal memory capture. In context, that makes the storage features more dangerous because it normalizes retention of interpersonal details not strictly needed for diary generation.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The trigger phrases are generic terms like 'journal', 'quotes', and 'write entry', which can cause accidental activation during unrelated conversations. In this skill, unintended activation matters because it can lead to persistence of conversation-derived content into memory files and downstream automation flows.

Ssd 3

Medium
Confidence
95% confidence
Finding
The template directly instructs the agent to document human interactions, memorable quotes, and evolving relationship details across sessions. This promotes creation of a persistent personal dossier that may include sensitive preferences, emotional context, and identifying anecdotes beyond what is necessary for the stated function.

Ssd 3

Medium
Confidence
97% confidence
Finding
The 'Quote Hall of Fame' explicitly tells the system to persist user statements for future retrieval and reuse. Stored quotations can contain sensitive, identifying, or context-dependent content that may later be exposed, misinterpreted, or replayed outside the original conversation context.

Ssd 3

Medium
Confidence
96% confidence
Finding
Relationship tracking instructions direct the agent to retain preferences, inside jokes, recurring themes, and collaboration patterns about the human. This is long-term behavioral profiling, which raises privacy and trust concerns and can produce unnecessarily intimate or revealing memory artifacts.

Ssd 3

Medium
Confidence
95% confidence
Finding
Memory integration and 'On This Day' resurfacing intentionally replicate diary content into broader memory logs and future entries. Replication increases the blast radius of sensitive content, makes deletion harder, and can reintroduce old private material into new contexts without fresh user approval.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
Run the job once with `openclaw automations run <job-id> --wait`. When an approval card appears, choose **Always allow** for each exact command the job runs. The grant also binds the command to the same working directory, environment, and automation configuration. Keep those values unchanged on later runs.

If no approval surface is connected, the `--auto` command is denied immediately. The run records an error, and repeated failures can disable the recurring job. Inspect disabled jobs with `openclaw automations list --all` and check the run history before enabling it again.

See the [OpenClaw automations documentation](https://docs.openclaw.ai/automation/cron-jobs) for other schedule and delivery options.
Confidence
84% confidence
Finding
The automation guidance instructs users to choose 'Always allow' for exact commands used by the recurring job. Persistent approval for scheduled execution increases the chance that future unattended runs will read session data and write reflective outputs without timely human review, especially if the surrounding workspace content becomes more sensitive over time.

Ssd 3

Medium
Confidence
93% confidence
Finding
Weekly digest generation aggregates prior quotes, decisions, mood trends, and observations into synthesized summaries. Aggregation increases sensitivity because it can reveal higher-level patterns and inferences that are more privacy-invasive than any single stored note.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- **Context Awareness:** Reads recent session logs and existing memory files for context

### v0.3.0
- **Auto-Setup:** `generate.py` now automatically runs setup wizard if no config.json exists
- **Memory Integration:** New feature to append diary summaries to main daily memory log (`memory/YYYY-MM-DD.md`)
  - Three formats: `summary`, `link`, `full`
  - Enabled by default during setup
Confidence
80% confidence
Finding
Automatically running setup and enabling memory-related behavior can cause persistent storage and configuration changes without a deliberate privacy review at the moment of first use. In this skill's context, even benign automation is riskier because it governs retention of sensitive journal and interaction data.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The configuration enables memory integration that appends diary-related content into daily records, which can aggregate sensitive personal reflections and behavioral data without any accompanying consent, notice, or privacy safeguards in the example configuration. In a diary skill, this materially increases the chance that intimate or identifying information is collected and persisted in ways users may not fully anticipate.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
Mood tracking and topic extraction are profiling features that infer emotional state and patterns from diary entries, which are highly sensitive by nature. Describing and enabling these analysis capabilities without clear warnings, opt-in consent, or data-handling safeguards creates privacy risk because users may not realize the extent of behavioral inference being performed.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code writes a synthesized report containing sensitive diary-derived mood, wins, and frustrations data to a user-specified file path. While it confirms after saving, there is no prior user-facing warning in the code that the output may contain sensitive personal information, and the save-to-file behavior is optional rather than inherently obvious from the skill purpose alone.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The manifest describes an AI diary generation skill with reflective journaling, analytics, resurfacing, and scheduled generation, but does not mention document conversion tooling or host-level command execution. This file invokes pandoc through subprocess to generate PDF/HTML exports, adding a capability beyond the stated purpose and one that is more sensitive than normal text generation behavior.

Static analysis

No suspicious patterns detected.