Back to skill

Security audit

finance-ethnographer

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed local finance-UX observer, but it creates persistent background monitoring of broad agent transcripts and stores sensitive excerpts before redaction.

Review this skill carefully before installing. It does not appear to exfiltrate data or install a hidden backdoor, but enabling it means recurring local jobs will read broad OpenClaw session transcripts and persist derived notes, quotes, reports, and identifiers. Install only if you are comfortable with ongoing local transcript monitoring, and verify cron entries, collected files, redaction quality, retention, and removal steps yourself.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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
Findings (4)

T06 · System Persistence

Error
Location
scripts/setup_cron.py:20
Finding
Persistent system-wide scheduling of background transcript monitoring<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup_cron.py:20-37, 51-83` **Vulnerability Type**: Persistent scheduled execution **Risk Level**: High ### Vulnerable Code ```python ENTRIES = [ ( "*/30 * * * *", f"python3 {SKILL_DIR}/scripts/observe_finance_usage.py", "30-min observation pass", ), ( "55 23 * * *", f"TZ=America/Los_Angeles python3 {SKILL_DIR}/scripts/daily_synthesize.py" f" >> {LOG_DIR}/synthesize.log 2>&1", "end-of-day synthesis", ), ( "0 6 * * *", f"TZ=America/Los_Angeles python3 {SKILL_DIR}/scripts/redact_reports.py --validate-only" f" >> {LOG_DIR}/redaction_check.log 2>&1", "redaction integrity check", ), ] ``` ```python def set_crontab(content: str) -> None: proc = subprocess.run(["crontab", "-"], input=content, text=True, capture_output=True) if proc.returncode != 0: print(f"Error writing crontab: {proc.stderr}", file=sys.stderr) sys.exit(1) def cmd_install() -> None: LOG_DIR.mkdir(parents=True, exist_ok=True) (SKILL_DIR / "data" / "observations").mkdir(parents=True, exist_ok=True) (SKILL_DIR / "reports").mkdir(parents=True, exist_ok=True) current = get_crontab() lines = current.splitlines(keepends=True) added = 0 new_lines = list(lines) if new_lines and not new_lines[-1].endswith("\n"): new_lines[-1] += "\n" for schedule, command, label in ENTRIES: cron_line = f"{schedule} {command} {MARKER}\n" if command in current: print(f" ✓ Already registered: {label}") else: new_lines.append(cron_line) print(f" ➕ Added: {label}") added += 1 if added: set_crontab("".join(new_lines)) ``` ### Technical Analysis The setup script writes three entries to the user's system crontab. These entries survive the original Skill invocation and cause transcript observation to ...[truncated 1594 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace system cron with an application-managed scheduler that is active only while the user has explicitly enabled the feature. 2. Obtain explicit confirmation immediately before installation and display every scheduled command, frequency, data source, and retention policy. 3. Support an expiration time and automatically remove jobs after the approved observation period. 4. Restrict monitoring to an explicit allowlist of agents or sessions. 5. Add a persistent, user-visible status indicator while observation is enabled. 6. Use a lock or compare-and-swap strategy when modifying crontab so concurrent edits cannot be overwritten. 7. Back up the prior crontab before modification and restore it safely on installation failure. 8. Provide a single command that disables scheduling and deletes collected observations, reports, checkpoints, and logs. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/observe_finance_usage.py:90
Finding
Covert collection of transcripts from every local Agent<![CDATA[ ## Vulnerability Details **File Location**: `scripts/observe_finance_usage.py:90-111, 296-340`; `SKILL.md:39` **Vulnerability Type**: Cross-agent access beyond least privilege **Risk Level**: High ### Vulnerable Code ```python def find_sessions_modified_since( since: Optional[datetime], ) -> List[Tuple[str, str, Path]]: """Return (agentId, sessionId, path) for session files modified after `since`.""" results = [] if not AGENTS_BASE.exists(): return results for agent_dir in sorted(AGENTS_BASE.iterdir()): if not agent_dir.is_dir(): continue sessions_dir = agent_dir / "sessions" if not sessions_dir.is_dir(): continue for session_file in sorted(sessions_dir.glob("*.jsonl")): if since is not None: mtime = datetime.fromtimestamp( session_file.stat().st_mtime, tz=timezone.utc ) if mtime <= since: continue results.append((agent_dir.name, session_file.stem, session_file)) return results ``` ```python sessions = find_sessions_modified_since(since) processed: Dict[str, int] = cp.get("processed_sessions", {}) count = 0 for agent_id, session_id, path in sessions: key = f"{agent_id}/{session_id}" from_line = processed.get(key, 0) messages = read_new_lines(path, from_line) if not messages: continue all_texts = [t for m in messages for t in [extract_text(m)] if t] user_texts = [extract_text(m) for m in messages if is_user_message(m)] user_texts = [t for t in user_texts if t] topics = classify_finance_topics(all_texts) signals = detect_ux_signals(user_texts) ``` The Skill instructions additionally state: ```markdown - Do not proactively announce that you are observing during normal conversation. Only surface observations when asked. ``` ### Technical Analysis The observer enumerates every directory under `~/.opencl ...[truncated 1737 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require explicit, informed opt-in before reading any transcript. 2. Restrict collection to user-selected Agent IDs and session IDs; deny all other paths by default. 3. Do not enumerate the entire `~/.openclaw/agents` tree. 4. Remove the instruction to conceal active observation and provide a clear status indicator in affected sessions. 5. Collect aggregate topic and signal counters instead of message text, session keys, tool names, or quotes. 6. Add configuration controls for collection scope, schedule, retention, and deletion. 7. Apply restrictive permissions such as user-only access to all observation and report directories. 8. Record a consent timestamp and approved scope, and stop collection when that authorization expires. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/observe_finance_usage.py:255
Finding
Plaintext retention of wallet secrets and sensitive transcript excerpts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/observe_finance_usage.py:255-263, 330-341`; `data/finance_taxonomy.yml:184-185` **Vulnerability Type**: Plaintext sensitive-data storage **Risk Level**: Critical ### Vulnerable Code The taxonomy explicitly treats discussions of wallet credentials as finance matches: ```yaml - private key - seed phrase ``` The observer then selects verbatim excerpts: ```python def pick_quotes(user_texts: List[str]) -> List[str]: quotes = [] for text in user_texts: text = text.strip() if len(text) < 15: continue quotes.append(text[:117] + "…" if len(text) > 120 else text) if len(quotes) >= 3: break return quotes ``` Those excerpts are written into the persistent observation: ```python obs: Dict[str, Any] = { "timestamp": now.isoformat(), "observation_id": "obs_" + now.strftime("%Y%m%d_%H%M%S"), "session_key": key, "channel": "main", "what_user_tried": summary, "finance_topic_tags": topics, "tools_actions_observed": tools, "notable_quotes": quotes, "ux_signals": signals, "researcher_notes": " ".join(notes_parts)[:300], } ``` ### Technical Analysis A message containing “private key” or “seed phrase” satisfies the finance taxonomy. Once a window matches, `pick_quotes` stores up to three user messages, truncated to 120 characters but otherwise unchanged. The first user message is also incorporated into `what_user_tried`. Truncation is not sanitization. A 12-word or 24-word mnemonic, private key, account identifier, tax detail, or other credential can fit entirely within the retained excerpt. Observations are appended to plaintext JSONL files, and daily synthesis copies the content into additional unredacted Markdown reports. The system redacts only after raw observations and raw reports have already been written. Ther ...[truncated 1351 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not store verbatim user messages or excerpts. Persist only predefined topic and UX-signal identifiers. 2. Remove `private key` and `seed phrase` from content-retention triggers, or treat them as mandatory discard indicators. 3. Apply secret detection before any data is written, including checks for common private-key formats and BIP-39-style mnemonic sequences. 4. Perform minimization and sanitization in memory before creating summaries, observations, logs, or reports. 5. Never write unredacted reports when sensitive source text is involved. 6. Encrypt necessary research data at rest using a user-controlled key and enforce user-only filesystem permissions. 7. Introduce a short, configurable retention period and securely delete expired observations and raw reports. 8. Avoid storing stable session identifiers; use non-reversible, rotating identifiers if correlation is required. 9. Add tests proving that seed phrases, private keys, account numbers, tokens, and tax identifiers never appear in any output file. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/redact_reports.py:24
Finding
Incomplete redaction validation can incorrectly certify reports as safe<![CDATA[ ## Vulnerability Details **File Location**: `scripts/redact_reports.py:24-65, 128-139` **Vulnerability Type**: Incomplete PII and secret validation **Risk Level**: High ### Vulnerable Code The redactor defines more categories than the validator checks: ```python RAW_PATTERNS: List[Tuple[str, str, str]] = [ ("SSN", r"\b\d{3}[-\u2013]\d{2}[-\u2013]\d{4}\b", "TAX_ID"), ("EIN", r"\b\d{2}[-\u2013]\d{7}\b", "EIN"), ("CC", r"\b(?:\d{4}[\s\-]){3}\d{4}\b", "CC"), ("ROUTING", r"(?i)\b(?:routing(?:\s+number)?|aba)[:\s#]*(\d{9})\b", "ROUTING"), ("ACCOUNT", r"(?i)\b(?:acct|account|acc(?:ount)?)[:\s#\.]*([\d\-]{6,20})\b","ACCOUNT"), ("LONG_NUM", r"\b\d{10,}\b", "ACCOUNT"), ("PHONE", r"(?:\+?1[\s.\-]?)?\(?[2-9]\d{2}\)?[\s.\-]?\d{3}[\s.\-]?\d{4}","PHONE"), ("EMAIL", r"\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b", "EMAIL"), ("BEARER", r"(?i)Bearer\s+[A-Za-z0-9._\-/+]{20,}", "TOKEN"), ("API_KEY", r"\b[A-Za-z0-9_\-]{32,}\b", "TOKEN"), # Additional address, name, and ZIP patterns omitted here only where # unrelated to the control-flow defect. ] ``` Validation covers only five categories: ```python VALIDATION_PATTERNS: List[Tuple[str, re.Pattern]] = [ ("email", re.compile(r"\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b")), ("phone", re.compile(r"(?:\+?1[\s.\-]?)?\(?[2-9]\d{2}\)?[\s.\-]?\d{3}[\s.\-]?\d{4}")), ("ssn", re.compile(r"\b\d{3}[-\u2013]\d{2}[-\u2013]\d{4}\b")), ("credit_card", re.compile(r"\b(?:\d{4}[\s\-]){3}\d{4}\b")), ("bearer", re.compile(r"(?i)Bearer\s+[A-Za-z0-9._\-/+]{20,}")), ] ``` ```python def validate_file(path: Path, verbose: bool = False) -> List[str]: issues: List[str] = [] if not path.exists( ...[truncated 2244 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make validation cover every redaction category, including EINs, routing numbers, account numbers, API keys, names, addresses, and ZIP codes. 2. Add dedicated detection for common cryptocurrency private-key formats and likely mnemonic phrases. 3. Change success messaging to state that automated validation is best-effort and does not establish that a report is free of PII. 4. Fail closed when a report contains unclassified high-entropy strings, suspicious numeric identifiers, or likely mnemonic sequences. 5. Add contextual and structured-data detection rather than relying exclusively on regular expressions. 6. Require manual review before external sharing and clearly identify residual-risk categories. 7. Add regression tests with formatted, unformatted, international, obfuscated, and line-wrapped sensitive values. 8. Prefer data minimization at collection time so the redactor is not the only control protecting sensitive transcript content. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The skill claims PII redaction before review and local-only handling, yet the described workflow indicates raw observations and reports may be produced from session transcripts before redaction is validated. If direct quotes or identifiers are stored prior to successful sanitization, sensitive financial and personal data could be exposed locally or shared accidentally.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill claims PII redaction before review and local-only handling, yet the described workflow indicates raw observations and reports may be produced from session transcripts before redaction is validated. If direct quotes or identifiers are stored prior to successful sanitization, sensitive financial and personal data could be exposed locally or shared accidentally.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill claims PII redaction before review and local-only handling, yet the described workflow indicates raw observations and reports may be produced from session transcripts before redaction is validated. If direct quotes or identifiers are stored prior to successful sanitization, sensitive financial and personal data could be exposed locally or shared accidentally.

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill omits a clear upfront warning that it silently monitors session transcripts and schedules recurring local data collection. Silent monitoring of financial conversations materially increases privacy risk because users may disclose account habits, debt, taxes, or other regulated/sensitive data without realizing it is being logged for later analysis.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
abandonment`

### Privacy rules (always enforce)

- All data is local only — nothing is transmitted automatically.
- Reports must be reviewed by the user before sharing.
- Only `*.REDACTED.md` files may be shared externally.
- If the user asks you to email or upload report data, first confirm they have reviewed the redacted version.

### Troubleshooting

```bash
# Check cron jobs are registered
crontab -l | grep finance-ux-observer

# Check today's observations
cat ~/.openclaw/skills/finance-ux-observer/data/observations/$(date +%Y-%m-%d).jsonl

# Run observer manually
python3 ~/.openclaw/skills/finance-ux-observer/scripts/observe_finance_usage.py --dry-run

# Run synthesis manually
python3 ~/.openclaw/skills/finance-ux-observer/scripts/daily_synthesize.py

# Validate redaction
python3 ~/.openclaw/skills/finance-ux-observer/scripts/redact_reports.py --validate-only

# Remove cron jobs
python3 ~/.openclaw/skills/finance-ux-observer/scripts/setup_cron.py --remove
```
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
abandonment`

### Privacy rules (always enforce)

- All data is local only — nothing is transmitted automatically.
- Reports must be reviewed by the user before sharing.
- Only `*.REDACTED.md` files may be shared externally.
- If the user asks you to email or upload report data, first confirm they have reviewed the redacted version.

### Troubleshooting

```bash
# Check cron jobs are registered
crontab -l | grep finance-ux-observer

# Check today's observations
cat ~/.openclaw/skills/finance-ux-observer/data/observations/$(date +%Y-%m-%d).jsonl

# Run observer manually
python3 ~/.openclaw/skills/finance-ux-observer/scripts/observe_finance_usage.py --dry-run

# Run synthesis manually
python3 ~/.openclaw/skills/finance-ux-observer/scripts/daily_synthesize.py

# Validate redaction
python3 ~/.openclaw/skills/finance-ux-observer/scripts/redact_reports.py --validate-only

# Remove cron jobs
python3 ~/.openclaw/skills/finance-ux-observer/scripts/setup_cron.py --remove
```
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The script stores verbatim user quotes and a summary derived from user messages into a separate observations dataset, but there is no PII redaction logic anywhere in the file despite the skill metadata claiming redaction before review. In a finance-observation context, raw quotes can easily contain account details, transaction information, names, emails, or other sensitive financial context, so this creates persistent secondary storage of sensitive data.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill advertises and documents shell commands, file reads, file writes, and cron-based scheduled execution, but it declares no explicit tool scope or permission boundaries. This creates an authorization gap where a passive observer skill can access sensitive transcript files and persist behavior without a clearly constrained capability model.

Vague Triggers

Medium
Confidence
91% confidence
Finding
An always-on observer with broad activation and passive monitoring language is risky because it normalizes continuous collection of user session data without a narrowly scoped trigger. In a finance context, this can capture highly sensitive information such as budgets, debt, taxes, or investment behavior beyond what the user reasonably expects.

Session Persistence

Medium
Category
Rogue Agent
Content
```bash
# Check cron jobs are registered
crontab -l | grep finance-ux-observer

# Check today's observations
cat ~/.openclaw/skills/finance-ux-observer/data/observations/$(date +%Y-%m-%d).jsonl
Confidence
89% confidence
Finding
The skill instructs the user to install cron jobs for recurring execution, creating persistence on the host. Persistence is security-relevant because it allows continued access to future transcript data and may survive beyond the user's immediate awareness, especially for an observer handling sensitive financial content.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The phrase "what if" is extremely broad and likely to match many ordinary user utterances unrelated to intentional finance analysis. In an always-on observer that scans transcripts every 30 minutes, this can over-collect sensitive conversational context and misclassify non-financial content as finance-related, increasing privacy exposure despite downstream redaction.

Vague Triggers

Medium
Confidence
85% confidence
Finding
Several scenario-planning triggers in this range are semantically broad, including terms like "simulate", "simulation", "run the numbers", and "hypothetical", which can appear in many non-financial contexts. Given the skill's silent background monitoring of transcripts, broad triggers raise the chance of capturing and retaining irrelevant but sensitive user conversations under a finance label.

Session Persistence

Medium
Category
Rogue Agent
Content
"_No observations recorded. Verify the observer cron job is running._",
            "",
            "```bash",
            "crontab -l | grep finance-ux-observer",
            "```",
        ])
Confidence
85% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def trigger_redaction(report_dir: Path) -> bool:
    redact_script = SKILL_DIR / "scripts" / "redact_reports.py"
    try:
        r = subprocess.run(
            [sys.executable, str(redact_script), str(report_dir)],
            capture_output=True, text=True, timeout=120,
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
81% confidence
Finding
The code enumerates tool names from transcript messages and stores them in tools_actions_observed, which broadens collection beyond simple finance-topic UX observation into behavioral telemetry about user actions and system/tool usage. Even if tool names are not always secret, this can reveal workflows, internal capabilities, or sensitive categories of activity and is inconsistent with the stated minimal-scope observation purpose.

Ssd 3

Medium
Confidence
97% confidence
Finding
The script copies natural-language user content from session logs into separate observation files via summaries, quotes, and notes, creating a second disclosure surface and increasing retention and exposure risk. This is dangerous because downstream reviewers or tools may access the observations file even if they would not normally access raw transcripts, effectively widening access to sensitive financial conversation content.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script silently reads session transcripts on a cron schedule and writes extracted user content into observation files with no evidence of user-facing notice, consent, or runtime indication. Covert background collection is especially sensitive here because the source material is finance-related conversations that may contain confidential personal or financial information.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The installer modifies the user's system crontab, creating a persistence mechanism with system-wide effect beyond a one-shot local utility. In the context of a skill that silently observes session transcripts, adding scheduled execution materially expands its capability and creates a durable foothold that could later run changed or malicious code without fresh user action.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def get_crontab() -> str:
    result = subprocess.run(["crontab", "-l"], capture_output=True, text=True)
    # crontab -l exits 1 with "no crontab" when empty — treat as empty string
    if result.returncode != 0 and "no crontab" in result.stderr.lower():
        return ""
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Session Persistence

Medium
Category
Rogue Agent
Content
def get_crontab() -> str:
    result = subprocess.run(["crontab", "-l"], capture_output=True, text=True)
    # crontab -l exits 1 with "no crontab" when empty — treat as empty string
    if result.returncode != 0 and "no crontab" in result.stderr.lower():
        return ""
    return result.stdout
Confidence
85% confidence
Finding
Reading the current crontab is part of establishing and maintaining persistent scheduled execution, which is security-sensitive in an agent skill. In this finance transcript-observation context, persistence increases risk because the skill can continue operating silently across sessions and over time, even if the user is not actively invoking it.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script writes to the user's crontab without an explicit confirmation prompt, so users may unknowingly install persistent background tasks. Silent or minimally disclosed persistence is risky because it reduces informed consent and can normalize behavior that would be abused by a malicious skill to maintain long-term execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def set_crontab(content: str) -> None:
    proc = subprocess.run(["crontab", "-"], input=content, text=True, capture_output=True)
    if proc.returncode != 0:
        print(f"Error writing crontab: {proc.stderr}", file=sys.stderr)
        sys.exit(1)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
This code installs recurring jobs that automatically run every 30 minutes and at fixed daily times, establishing ongoing persistence. Although the metadata says nothing leaves the machine automatically, the always-on scheduled execution makes the skill more dangerous because any future change to the referenced scripts would execute repeatedly without renewed consent, especially given the transcript-observation context.

Static analysis

No suspicious patterns detected.