Back to skill

Security audit

Workflow Crystallizer

Security checks for vulnerabilities and agentic risk

Overview

This skill is coherent and locally focused, but it persistently caches memory-log content and turns untrusted memory headings into agent-readable automation drafts without enough safeguards.

Install only if you are comfortable with this skill reading your OpenClaw memory logs and keeping a local plaintext cache of memory-derived excerpts. Review state.json periodically, avoid running it automatically until its triggers are narrowed, and treat generated cron definitions and skill drafts as untrusted drafts that need human review before approval.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/analyze_patterns.py:232
Finding
Plaintext Persistent Caching of Sensitive Memory Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyze_patterns.py:69-71, 232`; `scripts/analyze_patterns.py:514-516`; `scripts/state.py:57-67` **Vulnerability Type**: Plaintext storage of memory-derived sensitive data **Risk Level**: Medium ### Vulnerable Code ```python def parse_memory_file(filepath: Path) -> list[dict]: """Parse a memory file into structured events (one per H2 section).""" text = filepath.read_text(encoding="utf-8", errors="replace") ``` ```python return { "section": header, "keywords": keywords, "actions": actions, "entities": extract_entities(full_text), "has_steps": detect_steps(body), "time_hint": extract_time_hint(header, body), "day_of_week": day_of_week, "is_formalized": detect_formalized(header, body), "raw_summary": body[:500].strip(), } ``` ```python events = parse_memory_file(filepath) add_events(state, date_str, events) new_event_count += len(events) ``` ```python def save_state(state: dict, path: Optional[str] = None) -> None: """Write state to disk atomically.""" p = Path(path) if path else DEFAULT_STATE_PATH p.parent.mkdir(parents=True, exist_ok=True) tmp = p.with_suffix(".tmp") with open(tmp, "w") as f: json.dump(state, f, indent=2, default=str) tmp.replace(p) ``` ### Technical Analysis The analyzer reads Agent memory files and stores up to 500 characters from each parsed section in the `raw_summary` field. These excerpts are added to the persistent `event_cache` and serialized into `state.json` as plaintext. Atomic replacement protects the state file from partial writes but does not protect confidentiality. The implementation has no secret redaction, encryption, data minimization, expiration policy, or explicit restrictive file permissions. The resulting file permissions depend on the process umask and execution environment. Because Agent memory may contain private conversations, project information, credentials, tokens, inte ...[truncated 1361 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not persist `raw_summary` by default. Cache only normalized keywords, hashes, counts, and other minimum features required for clustering. 2. If summaries are required, make retention explicitly opt-in and document exactly what is stored. 3. Apply secret and personal-data redaction before serialization, including patterns for API keys, access tokens, credentials, private keys, email addresses, and internal URLs. 4. Enforce a configurable retention period and purge events older than the required analysis window. 5. Create the state and temporary files with permissions limited to the owner, such as mode `0600`. 6. Verify the parent directory is not group-writable or world-readable. 7. Consider encrypting persisted memory-derived content using an operating-system credential store or a user-managed key. 8. Ensure reset and uninstall procedures securely remove all cached memory-derived data. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
scripts/analyze_patterns.py:97
Finding
Untrusted Memory Headings Propagated into Agent-Readable Reports and Skill Drafts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyze_patterns.py:97-103, 455-458`; `scripts/generate_suggestions.py:148-152, 241-255`; `scripts/report.py:5, 96-100, 115-119` **Vulnerability Type**: Indirect prompt injection through untrusted memory content **Risk Level**: Medium ### Vulnerable Code ```python for line in text.split("\n"): if line.startswith("## "): if current_header is not None: sections.append((current_header, "\n".join(current_body))) current_header = line[3:].strip() current_body = [] ``` ```python "events": [ {"date": ev.get("date"), "section": ev.get("section")} for ev in cluster ], ``` ```python event_descriptions = [] for ev in events: event_descriptions.append( f"- {ev.get('date', '?')}: {ev.get('section', '?')}" ) evidence = "\n".join(event_descriptions) ``` ```python skill_draft = ( f"---\n" f"name: {skill_name}\n" f"description: \"Automates the recurring workflow: {label}. " f"Detected from {cluster['count']} occurrences across " f"{cluster['unique_days']} days. Trigger words: {keyword_str}.\"\n" f"---\n\n" f"# {label} Skill\n\n" f"Auto-generated skill draft from Workflow Crystallizer.\n" f"This pattern was detected {cluster['count']} times.\n\n" f"## Workflow\n\n" f"1. [Step 1 — fill in based on the pattern]\n" f"2. [Step 2]\n" f"3. [Step 3]\n\n" f"## When to Use\n\n" f"Trigger phrases: {keyword_str}\n\n" f"## Evidence\n\n" f"{evidence}\n" ) ``` ```python # Evidence evidence = sugg.get("evidence", "") if evidence: lines.append("**Evidence:**") lines.append(evidence) lines.append("") ``` ```python elif stype == "skill": skill_draft = impl.get("skill_draft", "") lines.append("**Draft SKILL.md:**") lines.append("```markdown") lines.append(skill_draft) lines.append("```") ``` ### Technical Analysis Memory headings are treated as trusted presentation ...[truncated 2407 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all memory headings and bodies as untrusted data rather than Agent instructions. 2. Place evidence inside a strongly delimited, escaped data structure and explicitly tell the consuming Agent that quoted content must never be followed as instructions. 3. Escape Markdown headings, links, HTML, backticks, and code-fence delimiters before report generation. 4. Reject or neutralize headings containing role impersonation, instruction-override language, tool-call syntax, or other control-like patterns. 5. Keep source content out of generated `SKILL.md` instruction sections. Store evidence in separate metadata or a non-executable review attachment. 6. Validate generated Skill drafts against a restrictive schema and require explicit human review before installation. 7. Require separate confirmation before registering any generated cron definition. 8. Preserve provenance for every generated field so downstream consumers can distinguish trusted template text from untrusted memory-derived content. 9. Add adversarial tests using headings that contain fake system messages, nested Markdown fences, XML-like tool calls, and instruction-override phrases. ]]>
Vulnerability Patterns
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Memory Manipulation

High
Category
Memory Poisoning
Content
state.setdefault("analysis_log", [])
            return state
        except (json.JSONDecodeError, KeyError):
            sys.stderr.write(f"Warning: Corrupt state file at {p}, starting fresh.\n")
            return EMPTY_STATE.copy()
    return EMPTY_STATE.copy()
Confidence
90% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Memory Manipulation

High
Category
Memory Poisoning
Content
import argparse
    parser = argparse.ArgumentParser(description="Inspect crystallizer state")
    parser.add_argument("--state-file", default=None, help="Path to state.json")
    parser.add_argument("--reset", action="store_true", help="Reset state to empty")
    args = parser.parse_args()

    if args.reset:
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documents and encourages scripts that read memory logs and write persistent state, but it declares no explicit tool scope or permissions boundary. That creates a mismatch between described capabilities and declared access, increasing the chance the agent is invoked with broader filesystem access than intended and enabling unintended reads or writes to sensitive local data.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger phrases are broad and overlap with common requests like workflow optimization, habits, recurring tasks, and automation suggestions, making accidental or overly frequent invocation more likely. Because this skill persists state and analyzes memory logs, overbroad triggering can cause it to process sensitive historical data in contexts where the user may not have intended that level of analysis.

Vague Triggers

Medium
Confidence
94% confidence
Finding
This markdown template instructs generated skills to use a one-line description 'with trigger words' and a 'Trigger phrases: [keywords from cluster]' section without requiring specificity, constraints, or negative examples. That can lead to skill activations based on broad keyword clusters that overlap with normal conversation, which matches the vague-trigger category for markdown files.

Session Persistence

Medium
Category
Rogue Agent
Content
def save_state(state: dict, path: Optional[str] = None) -> None:
    """Write state to disk atomically."""
    p = Path(path) if path else DEFAULT_STATE_PATH
    p.parent.mkdir(parents=True, exist_ok=True)
    tmp = p.with_suffix(".tmp")
Confidence
80% confidence
Finding
The skill persists analysis history, event cache, and suggestions to disk in a predictable JSON file without any permission hardening or confidentiality controls. In this skill context, that state likely contains sensitive user workflow and memory-log derived data, so other local users or processes could read or tamper with it if filesystem permissions are weak.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The footer string forces the report timestamp to be labeled as 'ET', which imposes a specific locale/timezone in user-facing output. The file does not offer a timezone choice or explain why Eastern Time is required, which fits the natural-language locale policy violation criteria.

Static analysis

No suspicious patterns detected.