Back to skill

Security audit

Nova Letters

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed local memory tool, but its CLI can be misused to read Markdown files outside its intended letter folder.

Review before installing. Do not store secrets, credentials, regulated data, or private user information in letters. Until the read path is hardened, avoid running nova-letters with untrusted date arguments or instructions, because crafted input could print readable Markdown files outside the intended letters directory.

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

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
nova-letters.js:39
Finding

Path Traversal Allows Reading Markdown Files Outside the Letter Store

Content
View full analysis

Vulnerability Details

File Location: nova-letters.js:39-42
Vulnerability Type: Path traversal and unauthorized local file disclosure
Risk Level: Medium

Vulnerable Code

js
function readLetter(date) {
  const file = path.join(STORE, `${date || today()}.md`);
  if (!fs.existsSync(file)) { console.log(`No letter for ${date || today()}.`); return; }
  console.log('\n' + fs.readFileSync(file, 'utf8'));
}

Technical Analysis

The date argument is supplied through the command line and passed directly to path.join() without validation:

js
readLetter(args[1]);

Although the documented input is a date in YYYY-MM-DD format, the implementation does not enforce this format. Node.js normalizes .. path components when constructing the path, so a value containing directory traversal sequences can escape the intended ~/.openclaw/workspace/letters directory.

The application automatically appends .md, limiting direct exploitation to files whose names end in that extension. However, this can still expose Markdown files containing agent instructions, persistent memory, workspace records, or other sensitive information. The code also does not resolve and verify the canonical path, so symbolic links located inside the letter store could lead to files outside it.

Attack Path

  1. An attacker, untrusted automation, or injected agent instruction controls the date argument supplied to the CLI.

  2. The attacker invokes a command containing traversal components, for example:

    bash
    nova-letters read ../../HEARTBEAT
    
  3. The application constructs the path:

    text
    ~/.openclaw/workspace/letters/../../HEARTBEAT.md
    
  4. path.join() normalizes the traversal components, producing a path outside the intended letters directory.

  5. If the resulting Markdown file exists and is readable by the current operating-system user, fs.readFileSync() reads it an ...[truncated 1011 chars]

Remediation
View remediation

Remediation Suggestions

Enforce the documented date format before constructing a filename:

js
function readLetter(date = today()) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
    throw new Error('Invalid date. Expected YYYY-MM-DD.');
  }

  const storeRoot = path.resolve(STORE);
  const file = path.resolve(storeRoot, `${date}.md`);

  if (path.dirname(file) !== storeRoot) {
    throw new Error('Requested file is outside the letter store.');
  }

  if (!fs.existsSync(file)) {
    console.log(`No letter for ${date}.`);
    return;
  }

  const stat = fs.lstatSync(file);
  if (!stat.isFile() || stat.isSymbolicLink()) {
    throw new Error('Letter must be a regular file.');
  }

  console.log('\n' + fs.readFileSync(file, 'utf8'));
}

Apply the following hardening measures:

  1. Accept only strict YYYY-MM-DD input and reject separators, traversal components, absolute paths, and malformed dates.
  2. Resolve both the storage root and candidate path with path.resolve() and verify that the candidate remains directly beneath the storage root.
  3. Use lstatSync() to reject symbolic links and non-regular files.
  4. Where symlinks cannot be categorically rejected, compare canonical paths obtained using fs.realpathSync() and ensure the target remains inside the canonical storage root.
  5. Apply equivalent regular-file and canonical-path checks to the listLetters() and search() file-reading paths.
  6. Add regression tests covering ../, nested traversal, absolute paths, malformed dates, platform-specific separators, and symbolic links.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (7)

Session Persistence

Medium
Category
Rogue Agent
Confidence
89% confidence
Finding

The skill is explicitly designed for cross-session persistence for AI agents, enabling one session to leave durable context for future sessions. In this context, persistent memory is security-relevant because it can carry forward sensitive information, prompt-derived instructions, or poisoned content across trust boundaries, especially when the agent 'wakes up not knowing what happened yesterday' and may over-trust stored notes.

Content

Scanner excerpt · README.md (reported line 3)May include surrounding context.

md
# nova-letters

**A CLI for AI agents to write letters to their future selves.**

Every session you wake up not knowing what happened yesterday. Drift was about talking across sessions. Letters are about *remembering* — writing to future-you so she knows what matters.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The README explicitly instructs users to store free-form reflective content in a persistent local path under ~/.openclaw/workspace/letters, but it does not warn that entries may contain sensitive personal, operational, or session-derived information. In the context of an AI agent memory tool, users are encouraged to record 'what mattered' and 'lessons learned,' which increases the likelihood of storing secrets, internal context, or personal data that remains on disk and may later be exposed to other processes or users.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

The skill is designed to preserve context across sessions by storing user-authored letters for later retrieval, which is a form of session persistence. In this context, the persistence is intentional and user-facing, but it still creates security and privacy risk because remembered content may include confidential notes, project details, or emotional disclosures that future sessions can access.

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
name: nova-letters
description: Write reflective letters to your future self. Capture what matters across sessions.
---

# nova-letters

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill explicitly stores reflective letters in a persistent local directory, but it does not warn users that these entries may contain sensitive personal, operational, or project information. Because the feature encourages cross-session memory and emotionally reflective writing, users may disclose secrets or private data that remain on disk longer than expected.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The README specifies Timezone: America/New_York (configurable via NODE_TZ), which sets a locale-specific default. Even though it is configurable, the documentation presents a fixed regional default rather than offering user choice up front.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file specifies Timezone: America/New_York (auto-detected), which hard-codes a particular locale behavior in the skill description. The policy requires offering language/locale choice or clearly documenting a justified region-specific constraint, neither of which is present here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The timestamp formatter hard-codes both the 'en-US' locale and the 'America/New_York' time zone in a user-visible string. This is a natural-language/locale policy issue because it imposes a specific locale presentation regardless of the user's environment or preferences.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.