Back to skill

Security audit

KnowledgeKeeper Skill

Security checks for vulnerabilities and agentic risk

Overview

KnowledgeKeeper is a straightforward local note-management skill with no network access or hidden agent-control behavior, but users should understand that notes are saved to a local file.

Install only if local note persistence is acceptable. Avoid running it from shared or attacker-writable directories, and do not use it for highly sensitive notes unless the storage path and file permissions are hardened.

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
main.py:28
Finding

Fixed State Path Allows Symlink-Based File Overwrite

Content
View full analysis

Vulnerability Details

File Location: main.py, lines 12 and 28-30
Vulnerability Type: Symlink following and unsafe state-file handling
Risk Level: Medium

Vulnerable Code

python
class KnowledgeKeeper:
    def __init__(self):
        self.notes_file = "knowledge.json"
        self.load_notes()
    
    def load_notes(self):
        """Load notes from file"""
        if os.path.exists(self.notes_file):
            with open(self.notes_file, 'r', encoding='utf-8') as f:
                self.notes = json.load(f)
        else:
            self.notes = {
                "categories": {},
                "tags": {},
                "notes": []
            }
    
    def save_notes(self):
        """Save notes to file"""
        with open(self.notes_file, 'w', encoding='utf-8') as f:
            json.dump(self.notes, f, indent=2, ensure_ascii=False)

Technical Analysis

The application stores state at the fixed relative path knowledge.json. It opens this path directly in write mode without verifying that it is a regular file, checking its ownership, rejecting symbolic links, or securely creating the destination.

Python's normal open(..., 'w') behavior follows symbolic links and truncates the linked target. Consequently, an attacker with write access to the process working directory can place a symbolic link at knowledge.json and redirect a subsequent save operation to another path.

An existing target generally must contain valid JSON with the expected note structure for initialization and a later mutation to succeed. Alternatively, a dangling symbolic link can cause a new target file to be created if the process has permission to write to the target directory.

Attack Path

  1. An attacker obtains write access to the directory from which the skill runs.
  2. The attacker creates knowledge.json as either:
    • a symbolic link to an existing writable file conta ...[truncated 882 chars]
Remediation
View remediation

Remediation Suggestions

  • Store state in a dedicated application-data directory rather than the current working directory.
  • Create that directory with restrictive permissions and verify its ownership before use.
  • Use os.lstat() to detect and reject symbolic links at the destination.
  • Open files using platform-appropriate no-follow protections such as os.open() with O_NOFOLLOW where available.
  • Write data to a securely created temporary file in the same trusted directory, flush and synchronize it, and then atomically replace the destination with os.replace().
  • Apply restrictive file permissions, such as owner-only read and write access, where appropriate.
  • Avoid executing the skill with elevated privileges.

T09 · Insecure Skill Coding Practices

Note
Location
main.py:34
Finding

Second-Resolution Note IDs Permit Collisions and Ambiguous Updates

Content
View full analysis

Vulnerability Details

File Location: main.py, line 34, with affected indexing and update logic at lines 49-57 and 84-119
Vulnerability Type: Predictable and collision-prone identifier generation
Risk Level: Low

Vulnerable Code

python
def add_note(self, title, content, category=None, tags=None):
    """Add a new note"""
    note_id = f"note_{int(time.time())}"
    note = {
        "id": note_id,
        "title": title,
        "content": content,
        "category": category,
        "tags": tags or [],
        "created_at": time.strftime("%Y-%m-%d %H:%M:%S"),
        "updated_at": time.strftime("%Y-%m-%d %H:%M:%S")
    }
    
    self.notes["notes"].append(note)
    
    # Update categories
    if category:
        if category not in self.notes["categories"]:
            self.notes["categories"][category] = []
        self.notes["categories"][category].append(note_id)
    
    # Update tags
    for tag in tags or []:
        if tag not in self.notes["tags"]:
            self.notes["tags"][tag] = []
        self.notes["tags"][tag].append(note_id)

The update operation treats the identifier as unique and modifies the first match:

python
def update_note(self, note_id, title=None, content=None, category=None, tags=None):
    """Update an existing note"""
    for note in self.notes["notes"]:
        if note["id"] == note_id:
            if title:
                note["title"] = title
            if content:
                note["content"] = content
            # ...
            self.save_notes()
            return f"Note updated successfully"

Technical Analysis

Note identifiers are generated from int(time.time()), which has one-second resolution. Any notes created during the same second receive an identical identifier. No uniqueness check is performed before insertion.

Category and tag indexes store these non-unique IDs, whi ...[truncated 1370 chars]

Remediation
View remediation

Remediation Suggestions

  • Replace timestamp-derived identifiers with collision-resistant identifiers, such as uuid.uuid4().
  • Verify that a generated identifier is absent from the existing note collection before insertion.
  • Enforce identifier uniqueness in the persisted data model.
  • Reject or repair duplicate identifiers when loading existing state.
  • If concurrent handlers can access the same state file, add inter-process locking or use a transactional datastore to prevent lost updates and race conditions.
  • Add tests that create multiple notes in rapid succession and confirm that every note receives a distinct identifier.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (2)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill description is broad enough to activate for many generic requests such as creating notes, organizing information, searching content, or exporting data. Overly broad invocation criteria can cause inappropriate tool selection, exposing user data to an unnecessary skill and increasing the chance of unintended actions or policy bypass through capability overreach.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

This code writes note content to a local file on disk (knowledge.json), but there is no confirmation prompt, visible logging, or explicit user-facing warning that note data will be persisted. While persistence is consistent with the skill's purpose, the file operation itself is silent and the surrounding handler text does not disclose storage behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.