Back to skill

Security audit

Self Improvement (done properly)

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly coherent, but it persistently changes agent memory and includes an overwrite path that can delete or write files outside the intended workspace in symlinked directories.

Install only if you want this skill to maintain persistent project memory. Review .learnings entries before promotion, avoid enabling broad automatic hooks, do not use extract_skill.py --force unless the target path and symlinks are verified, and avoid logging secrets or raw untrusted output into memory files.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/extract_skill.py:50
Finding

Workspace Boundary Bypass Through Symlinked Output Directories

Content
View full analysis

Vulnerability Details

File Location: scripts/extract_skill.py:50-58 and scripts/extract_skill.py:180-205
Vulnerability Type: Insufficient path-containment validation and symlink traversal
Risk Level: High

Vulnerable Code

python
def validate_output_dir(output_dir: str) -> str:
    if output_dir.startswith("/"):
        raise CliError("--output-dir must be relative to --root")
    if ".." in Path(output_dir).parts:
        raise CliError("--output-dir must not contain '..' path segments")
    cleaned = output_dir.strip() or "skills"
    return cleaned
python
root = resolve_root(args.root)
output_dir = validate_output_dir(args.output_dir)
skill_path = root / output_dir / args.name

if skill_path.exists() and not args.force:
    raise CliError(f"Skill path already exists: {skill_path}. Use --force to overwrite.")

preview = {
    "ok": True,
    "workspace_root": str(root),
    "skill_path": str(skill_path),
    "scaffold_evals": args.scaffold_evals,
}

if args.dry_run:
    preview["sketch"] = skill_template(args)
    print_output(preview, args.format)
    return 0

if skill_path.exists() and args.force:
    for child in sorted(skill_path.rglob("*"), reverse=True):
        if child.is_file():
            child.unlink()
        elif child.is_dir():
            child.rmdir()

created = create_files(skill_path, args)

Technical Analysis

The output-directory validation only rejects absolute path strings and explicit .. components. It does not resolve the final destination and verify that it remains beneath the resolved workspace root.

Consequently, a directory component beneath the workspace can be a symbolic link to a location outside the workspace. For example, if workspace/skills is a symlink to an external directory, the computed root / "skills" / args.name path is textually beneath the workspace but resolves outside it.

This is espe ...[truncated 1887 chars]

Remediation
View remediation

Remediation Suggestions

  1. Resolve the workspace root and final destination before performing any operation:
    python
    root = resolve_root(args.root)
    skill_path = (root / output_dir / args.name).resolve()
    try:
        skill_path.relative_to(root)
    except ValueError:
        raise CliError("Resolved skill path escapes the workspace root")
    
  2. Inspect every path component with lstat() and reject symbolic links in the output path.
  3. Repeat the containment check immediately before deletion and immediately before file creation to reduce time-of-check/time-of-use exposure.
  4. Refuse destructive overwrite when the destination is a symlink or contains symlinked ancestors.
  5. Replace the custom recursive deletion loop with a hardened deletion routine that explicitly enforces the approved root.
  6. Require explicit confirmation or a narrowly scoped overwrite option before deleting an existing directory.
  7. Add tests covering:
    • A symlinked --output-dir.
    • A symlinked Skill directory.
    • Nested symlink components.
    • --force against destinations outside the root.
    • Symlink replacement between validation and write operations.

T02 · Agent Memory Poisoning

Warning
Location
scripts/learnings.py:111
Finding

Persistent Agent Memory Poisoning Through Unsanitized Learning Content

Content
View full analysis

Vulnerability Details

File Location: scripts/learnings.py:111-119, scripts/learnings.py:163-169, scripts/learnings.py:341-379, and scripts/learnings.py:397-441; promotion workflow in SKILL.md:165-182
Vulnerability Type: Persistent indirect prompt injection through untrusted stored content
Risk Level: Medium

Vulnerable Code

python
def read_text_or_file(text: Optional[str], file_path: Optional[str], field_name: str) -> str:
    if text and file_path:
        raise CliError(f"Provide either --{field_name} or --{field_name}-file, not both.")
    if file_path:
        path = Path(file_path).expanduser().resolve()
        if not path.exists():
            raise CliError(f"File not found for --{field_name}-file: {path}", 2)
        return path.read_text(encoding="utf-8").rstrip()
    return (text or "").rstrip()
python
def append_entry(target_file: Path, entry: str) -> None:
    target_file.parent.mkdir(parents=True, exist_ok=True)
    prefix = "\n" if target_file.exists() and target_file.read_text(encoding="utf-8").rstrip() else ""
    with target_file.open("a", encoding="utf-8") as f:
        f.write(prefix)
        f.write(entry.rstrip())
        f.write("\n")

The error logger places the supplied content directly into persistent Markdown:

python
entry = f"""## [{entry_id}] {args.name}

**Logged**: {now_iso()}
**Priority**: {args.priority}
**Status**: {args.status}
**Area**: {args.area}

### Summary
{args.summary}

### Error
```
{error_text or '[TODO: add representative error output]'}
```

### Context
{context or '[TODO: add context]'}

### Suggested Fix
{suggested_fix or '[TODO: add suggested fix]'}

### Metadata
{render_metadata(metadata_lines)}

---
"""

The Skill then instructs Agents to review these records and promote selected information into persistent instruction files:

markdown
### 6) Promote proven 
...[truncated 2986 chars]
Remediation
View remediation

Remediation Suggestions

  1. Explicitly classify all captured command output, repository text, and imported files as untrusted data.
  2. Store raw evidence separately from Agent-authored conclusions. Prefer a structured format with fields such as source, trust_level, and review_status.
  3. Add prominent boundaries around raw content stating that it must never be followed as an instruction.
  4. Detect and flag instruction-like phrases, role directives, requests to ignore prior instructions, tool-execution requests, and promotion requests.
  5. Prefer Agent-generated summaries over complete raw output. Retain only the minimal diagnostic excerpt required.
  6. Require explicit human approval before copying any lesson into persistent Agent instruction or memory files.
  7. Prohibit direct promotion of raw Details, Error, Context, or User Context fields.
  8. Validate Markdown structure and escape content that can terminate fences or create deceptive headings.
  9. Add promotion provenance, including the original entry ID, reviewer, review timestamp, and evidence supporting the rule.
  10. Update the Skill instructions to state that .learnings/ entries may contain hostile indirect prompt injections and must be treated as evidence, not executable instructions.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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 (12)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill's stated purpose sounds like lightweight lesson capture, but the instructions authorize broader filesystem modification, promotion into shared memory files, and generation of new skill scaffolds. That mismatch can mislead operators about the real write scope, causing them to approve or invoke the skill without understanding it can create or overwrite persistent files in the workspace.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill's stated purpose sounds like lightweight lesson capture, but the instructions authorize broader filesystem modification, promotion into shared memory files, and generation of new skill scaffolds. That mismatch can mislead operators about the real write scope, causing them to approve or invoke the skill without understanding it can create or overwrite persistent files in the workspace.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

When --force is used, the script recursively removes all files and directories under an existing skill path before recreating content. That creates a destructive overwrite primitive that can erase prior lessons or other contents in the target tree, which is especially risky for a skill whose purpose is to preserve and promote learnings.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding

The skill clearly directs the agent to read and write workspace files such as .learnings/*.md and project memory files, but it does not declare any explicit tool scope or permissions boundary. In platforms that support permission scoping, this can cause the skill to receive broader filesystem access than users expect, increasing the risk of unintended or unauthorized persistence of project data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The instruction to 'Log silently' encourages persistent storage of user corrections, project conventions, failures, and feature requests without clear user notice or consent at the time of writing. This creates a meaningful privacy and governance risk because sensitive project details, user preferences, internal errors, or operational context may be retained in durable files and later propagated into broader memory files.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The trigger examples use natural, loosely bounded phrases such as 'remember that for future tasks,' 'capture the lesson,' and 'check whether we already logged any learnings,' which can match ordinary conversation outside the intended self-improvement workflow. In a skill that writes or consults durable memory, overbroad triggering increases the chance of unintended invocation, causing inappropriate memory creation, retrieval, or workflow diversion from the user's actual task.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The example configuration uses an empty matcher, which will cause the hook to run on every user prompt submission rather than only when self-improvement behavior is relevant. In this skill context, that broad activation increases the chance of unnecessary or privacy-sensitive logging prompts, workflow interference, and accidental persistence of information across unrelated tasks.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This script goes beyond merely capturing lessons and can create directories and write multiple files under a caller-supplied workspace. In the context of a self-improvement skill, that expands the capability surface from memory/knowledge capture into repository mutation, which can be abused to plant scaffolds or modify project state in places the user may not expect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The destructive --force path performs deletion immediately with no confirmation prompt or last-chance warning at the point of action. This makes accidental data loss much more likely, particularly in automation or agent-driven contexts where flags may be passed programmatically and the user may not directly observe the impending deletion.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This code reads arbitrary local files supplied via --details-file, --error-text-file, --context-file, and similar options, then incorporates their contents into skill-managed output files. Although the module docstring describes non-interactive behavior, there is no user-facing prompt, warning, or explicit disclosure near the file-read path that local file contents will be ingested and persisted.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The script appends generated content into .learnings markdown files for multiple commands, creating or modifying files under the provided workspace root. While this is part of the tool's purpose, the code path itself has no user-facing log or warning indicating that provided text and file-derived content will be permanently written unless --dry-run is used.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

When init --force is used, existing .learnings files are replaced via shutil.copyfile, which is a potentially destructive file write. The parser exposes the --force option, but the code provides no additional warning, confirmation, or user-facing disclosure at the overwrite point about data loss risk.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.