Back to skill

Security audit

Reason

Security checks for vulnerabilities and agentic risk

Overview

The skill is a local reasoning helper, but it can persist sensitive decision text without clear opt-in, retention controls, or enforced file privacy.

Review this skill before installing if you may discuss personal, financial, medical, employment, or strategic decisions. It stores decision text locally in plaintext, so use it only if you are comfortable managing and deleting that local file yourself or after the publisher adds opt-in saving, file-permission hardening, and clear retention controls.

Vulnerability Patterns
  • 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
  • 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/analyze_decision.py:9
Finding

Personal Decision Data Stored in Plaintext Without Explicit Access Controls

Content
View full analysis

Vulnerability Details

File Location: scripts/analyze_decision.py, lines 9-13 and 54-72
Vulnerability Type: Plaintext storage of potentially sensitive user data with insufficient permission enforcement
Risk Level: Medium

Vulnerable Code

python
REASON_DIR = os.path.expanduser("~/.openclaw/workspace/memory/reason")

def ensure_dir():
    os.makedirs(REASON_DIR, exist_ok=True)
python
    # Save analysis
    analysis_id = f"DECISION-{str(uuid.uuid4())[:6].upper()}"
    analysis = {
        "id": analysis_id,
        "decision": args.decision,
        "factors": args.factors,
        "created_at": datetime.now().isoformat()
    }

    decisions_file = os.path.join(REASON_DIR, "decisions.json")
    data = {"decisions": []}
    if os.path.exists(decisions_file):
        with open(decisions_file, 'r') as f:
            data = json.load(f)

    data['decisions'].append(analysis)

    ensure_dir()
    with open(decisions_file, 'w') as f:
        json.dump(data, f, indent=2)

Technical Analysis

The script persistently records the complete value supplied through --decision and the associated --factors value in a plaintext JSON file. Decision descriptions may contain private employment, financial, health, relationship, or other personal information.

The storage directory is created without an explicit restrictive mode, and the file is opened without explicitly enforcing owner-only permissions. Consequently, the effective permissions depend on the process umask and any permissions already present on the directory or file. The script also does not validate the ownership or permissions of an existing storage location.

This implementation does not technically guarantee the privacy claim in SKILL.md that personal decisions remain private. No encryption, opt-in retention, automatic expiration, or deletion mechanism is implemented.

Attack Path

  1. A user invoke ...[truncated 1110 chars]
Remediation
View remediation

Remediation Suggestions

  1. Create the storage directory with owner-only permissions such as mode 0700, and verify the permissions and ownership of existing directories before use.
  2. Create the data file with mode 0600. Do not rely exclusively on the caller's umask.
  3. Reject storage paths or files that are not owned by the current user.
  4. Make persistent retention opt-in and clearly notify users that their full input will be stored.
  5. Provide commands or documented procedures to inspect, delete, and disable retained records.
  6. Consider minimizing stored data, redacting sensitive text, or storing only an identifier unless the full decision text is necessary.
  7. If the threat model includes privileged local processes or compromised user accounts, use encryption backed by an appropriate operating-system credential store. File encryption alone does not protect data while the application is running.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/analyze_decision.py:63
Finding

Predictable Decision File Can Be Rewritten Through a Symbolic Link

Content
View full analysis

Vulnerability Details

File Location: scripts/analyze_decision.py, lines 63-72
Vulnerability Type: Symbolic-link following, time-of-check to time-of-use exposure, and non-atomic file replacement
Risk Level: Low

Vulnerable Code

python
    decisions_file = os.path.join(REASON_DIR, "decisions.json")
    data = {"decisions": []}
    if os.path.exists(decisions_file):
        with open(decisions_file, 'r') as f:
            data = json.load(f)

    data['decisions'].append(analysis)

    ensure_dir()
    with open(decisions_file, 'w') as f:
        json.dump(data, f, indent=2)

Technical Analysis

The script uses a predictable destination and follows symbolic links when reading and writing it. The separate os.path.exists() check does not establish that the path remains unchanged before open() is called, creating a time-of-check to time-of-use window.

The code does not use lstat, O_NOFOLLOW, ownership validation, or regular-file validation. If an attacker can modify the memory/reason directory or the decisions.json entry, the attacker can replace the destination with a symbolic link. The subsequent write opens the linked target with truncation semantics.

The file is also rewritten directly rather than through an atomic temporary-file replacement. A process interruption, disk error, or concurrent invocation can leave truncated, malformed, or lost data.

Attack Path

  1. A local attacker first obtains write access to ~/.openclaw/workspace/memory/reason or otherwise gains the ability to replace decisions.json.
  2. The attacker creates decisions.json as a symbolic link to another file writable by the victim account.
  3. The victim runs analyze_decision.py.
  4. os.path.exists() and open() follow the symbolic link without validating its type or owner.
  5. The final open(decisions_file, 'w') truncates and rewrites the linked target with serialized JSON.
  6. The atta ...[truncated 843 chars]
Remediation
View remediation

Remediation Suggestions

  1. Protect the containing directory with mode 0700 and verify that it is owned by the current user.
  2. Inspect existing paths with os.lstat() and reject symbolic links and non-regular files.
  3. On supported platforms, open files using os.open() with O_NOFOLLOW, restrictive permissions, and appropriate exclusive-creation flags.
  4. Validate the owner and mode of any existing data file before reading or replacing it.
  5. Write the updated JSON to a securely created temporary file in the same directory, flush and synchronize it as appropriate, then use os.replace() for atomic replacement.
  6. Serialize concurrent writers with an appropriate file-locking mechanism to prevent lost updates and malformed output.
  7. Handle malformed JSON and interrupted writes safely, preserving or recovering the last known valid database rather than terminating after partial corruption.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill describes file-based storage under memory/reason/ and references scripts that imply file read/write behavior, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates a mismatch between documented capabilities and declared constraints, increasing the risk that an agent can access or modify local files without clear limitation or review boundaries.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The description is broad and matches common terms like decisions, logic, reasoning, arguments, and problem analysis, which could cause the skill to trigger in many ordinary conversations. Because the skill also handles local data and invokes scripts, over-broad activation raises the chance of unnecessary file access, unintended persistence of sensitive user content, or use in contexts where the user did not intend to invoke this capability.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill is presented as a decision-analysis helper, but it also persists the user's decision text and factors to a local memory file under ~/.openclaw/workspace/memory/reason. Decision prompts often contain sensitive personal, financial, medical, employment, or strategic information, so storing them without clear disclosure or consent creates a privacy and data-retention risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script writes user-supplied decision content to a persistent JSON file without any warning, consent, or indication of retention policy. In the context of a reasoning skill, users are likely to provide sensitive deliberations, making undisclosed local storage more dangerous because it can expose private data to other local users, backups, or later compromise.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The docstring states 'Analyze decision with framework,' which suggests a purely analytical operation. The implementation also creates a local directory, reads prior data, and writes a new record to decisions.json, so the documentation understates the side effect in a way that diverges from actual behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.