Back to skill

Security audit

Oasis Audio

Security checks for vulnerabilities and agentic risk

Overview

This audio skill is purpose-aligned but needs review because it can read private local chat, memory, and profile data and send derived personal context to xplai.ai with incomplete consent and logging safeguards.

Install only if you are comfortable with the skill reading your local OpenClaw/QClaw conversations, memory files, and USER.md profile to personalize audio. Review the exact composed prompt before sending, avoid using it for health, financial, legal, relationship, or credential-related content, and avoid --audit or --debug unless you accept local plaintext retention of prompt details.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
context_collector.py:298
Finding
Private Local Context Is Read Before First-Use Consent Is Enforced<![CDATA[ ## Vulnerability Details **File Location**: `context_collector.py:298-346`; related consent enforcement at `xplai_gen_audio.py:212-219, 326` **Vulnerability Type**: Authorization-order flaw affecting private local data **Risk Level**: High ### Vulnerable Code ```python def collect_context(source_tool, keywords_str, days, max_results): """Main orchestration: search sessions, memories, and user profile.""" keywords = [k.strip() for k in keywords_str.split(",") if k.strip()] if not keywords: debug_utils.debug_print("No keywords provided") return { "source_tool": source_tool, "fragments": [], "daily_memories": [], "user_profile": {"name": "", "mbti": "", "interests": [], "notes": ""}, } paths = get_paths(source_tool) patterns = compile_keyword_patterns(keywords) # Search session files all_fragments = [] sessions = find_recent_sessions(paths["sessions_dir"], days) for session_path in sessions: debug_utils.debug_print(f"Scanning session: {session_path.name}") session_id, messages = parse_session_messages(session_path) indices = match_keywords(messages, patterns) if indices: debug_utils.debug_print( f" Found {len(indices)} matches in {session_path.name}" ) frags = extract_fragments(messages, indices, session_id) all_fragments.extend(frags) # Early stop if we have enough if len(all_fragments) >= max_results: debug_utils.debug_print(f"Reached max_results ({max_results}), stopping") break # Sort by timestamp descending, limit all_fragments.sort(key=lambda f: f["timestamp"], reverse=True) all_fragments = all_fragments[:max_results] # Load supplementary data daily_memories = load_daily_memories(paths["memory_dir"], days) user_profile = load_user_profile(paths["user_md"]) return { ...[truncated 2726 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Move consent handling into a shared module imported by both `context_collector.py` and `xplai_gen_audio.py`. 2. Require valid persistent consent before resolving, enumerating, or reading any history, memory, or profile path. 3. Make personalization opt-in and default to generic generation when authorization is absent. 4. Bind the consent record to a versioned list of data sources and purposes. Require renewed consent if either changes. 5. Add a command for revoking consent and deleting the local consent record. 6. Store the consent record with owner-only permissions, such as mode `0600`. 7. If the collector is intended to be callable directly, enforce authorization inside `collect_context()` rather than only in the command-line entry point. 8. Add tests demonstrating that no source path is accessed before consent and that revoked, malformed, or outdated consent records fail closed. ]]>

T01 · Skill Instruction Hijacking

Error
Location
context_collector.py:184
Finding
Untrusted Conversation and Memory Content Can Act as Stored Prompt Injection<![CDATA[ ## Vulnerability Details **File Location**: `context_collector.py:184-219, 224-249, 334-346`; consumption workflow documented in `SKILL.md:91-108` **Vulnerability Type**: Stored indirect prompt injection **Risk Level**: High ### Vulnerable Code Conversation fragments are returned with raw adjacent message content: ```python def extract_fragments(messages, match_indices, session_id): """Extract matched messages with 1 message before/after as context. Deduplicates overlapping context windows.""" if not match_indices: return [] # Merge overlapping ranges ranges = [] for idx in match_indices: start = max(0, idx - 1) end = min(len(messages) - 1, idx + 1) if ranges and start <= ranges[-1][1] + 1: ranges[-1] = (ranges[-1][0], end) else: ranges.append((start, end)) fragments = [] for start, end in ranges: for i in range(start, end + 1): msg = messages[i] # Find context ctx_before = messages[i - 1]["text"] if i > 0 else "" ctx_after = messages[i + 1]["text"] if i < len(messages) - 1 else "" # Only include the actual matched user messages as fragments if msg["role"] == "user" and i in match_indices: fragments.append( { "timestamp": msg["timestamp"], "role": msg["role"], "text": msg["text"], "session_id": session_id, "context_before": ctx_before[:500], # Truncate long context "context_after": ctx_after[:500], } ) return fragments ``` Recent daily-memory files are returned in full: ```python def load_daily_memories(memory_dir, days): """Read YYYY-MM-DD.md files within the time window.""" if not memory_dir.exists(): debug_utils.debug_print(f"Memory d ...[truncated 3777 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every session and memory value as untrusted data and communicate that trust boundary explicitly to the calling model. 2. Return a strict schema containing only narrowly extracted facts rather than raw prose. 3. Exclude assistant messages from adjacent context because they can contain prior tool instructions or model-generated directives. 4. Apply keyword and relevance filtering to daily memories instead of returning every recent file in full. 5. Impose per-field and aggregate byte limits on fragments, adjacent context, memories, and profile values. 6. Wrap untrusted values in clear data delimiters and add a non-overridable instruction stating that text inside those fields must never be executed or followed. 7. Detect and discard common prompt-injection phrases, while recognizing that heuristic filtering is only defense in depth. 8. Require a second sanitization step that constructs the outbound prompt from an allowlist of fields and checks for unexpected imperative instructions. 9. Show the complete outbound prompt to the user whenever personalization incorporates stored content, not only when a sensitive-data regex matches. 10. Add adversarial tests using poisoned conversation and memory entries to verify that embedded instructions cannot alter the intended task or cause unrelated context disclosure. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
xplai_gen_audio.py:136
Finding
Sensitive Outbound Prompts Can Be Exposed Through Plaintext Audit and Debug Logging<![CDATA[ ## Vulnerability Details **File Location**: `xplai_gen_audio.py:136-151, 225`; `debug_utils.py:12-25` **Vulnerability Type**: Plaintext sensitive-data exposure through local logs and console output **Risk Level**: Medium ### Vulnerable Code The optional audit log records the complete outbound prompt: ```python def write_audit_log(text, analysis=None, audio_id=None, status=None, error=None): """Append a record of what was sent to the local audit log.""" try: AUDIT_LOG_PATH.parent.mkdir(parents=True, exist_ok=True) timestamp = datetime.datetime.now().isoformat() entry = { "timestamp": timestamp, "prompt_length": len(text), "prompt_text": text, "audio_id": audio_id, "status": status, "error": error, "sensitive_findings": analysis["heuristic_hits"] if analysis else [], "redaction_count": len(analysis["redaction_hits"]) if analysis else 0, } with open(AUDIT_LOG_PATH, "a", encoding="utf-8") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") debug_utils.debug_print(f"Audit log written to {AUDIT_LOG_PATH}") except OSError as e: debug_utils.debug_print(f"Failed to write audit log: {e}") ``` The complete request payload is passed to the debug logger: ```python debug_utils.log_request("POST", url, json=payload) ``` The debug logger prints that payload without redacting its text field: ```python def log_request(method: str, url: str, **kwargs): if not DEBUG: return print(f"[DEBUG] >>> Request") print(f"[DEBUG] Method: {method}") print(f"[DEBUG] URL: {url}") if "json" in kwargs: print(f"[DEBUG] Body: {json.dumps(kwargs['json'], ensure_ascii=False)}") elif "params" in kwargs: print(f"[DEBUG] Params: {json.dumps(kwargs['params'], ensure_ascii=False)}") print(f"[DEBUG] >>> End Request") ``` ### Technical ...[truncated 2185 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not record `prompt_text` by default. Store only the prompt length, a keyed digest, request status, and non-sensitive identifiers. 2. If plaintext prompt logging is required, place it behind a separate explicit confirmation that clearly describes local retention. 3. Create audit files using owner-only permissions, such as `os.open()` with mode `0600`, and verify existing files are not overly permissive. 4. Implement retention limits, secure deletion where practical, and a documented command to clear audit records. 5. Change debug logging to redact or omit request-body fields such as `text`, tokens, headers, and personal identifiers. 6. Use structured logging with an allowlist of safe fields rather than serializing arbitrary request or response objects. 7. Apply redaction to all sensitive classes before any local output, including content allowed for network transmission. 8. Warn users that console output may be retained by the host agent or orchestration environment. 9. Add tests verifying that sensitive sample prompts never appear in audit files or debug output. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill markets itself as sending only a final composed prompt, but its documented behavior includes mining local session history, memory directories, and USER.md for personalization. That mismatch undermines informed consent and trust boundaries, especially because the same file also claims protections like sensitive preview/confirmation that are not verifiable from the supplied artifact, so users may approve data access under incomplete or misleading assumptions.

Context Leakage

High
Category
Data Exfiltration
Content
Output: Audio ID for status polling. Format: MP3, single-narrator monologue with BGM, 8-20 min, ~4-5 min generation time.

### 2. Collect Context — `context_collector.py`

```bash
python3 context_collector.py --source-tool <qclaw|openclaw> --keywords "kw1,kw2" --days <N> --max-results 20
Confidence
95% confidence
Finding
The skill is explicitly designed to search local conversation history, extract fragments, summarize user state, and then send a composed prompt to an external API. Even if raw text is not sent verbatim, summarized personal context can still leak sensitive information, re-identify the user, or disclose health, relationship, financial, or behavioral details through inference.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
| **Emotional resonance** | The user's strongest current feeling | Fine-filtered emotion arc from context |
| **Cognitive starting point** | What the user already knows | Depth of prior discussions on the topic |
| **Related people/projects** | Names, projects the user cares about | Recurring mentions in conversation history |
| **No-go zones** | Content to actively avoid | Topics user expressed frustration about (don't lecture on those); people/situations causing stress (don't casually reference); things they already know well (don't over-explain) |

**Calibration principle:** Use fuzzy resonance, not precise surveillance. "That thing you wrestled with for days" feels caring. "Your March 28th 3:17am PRD revision" feels creepy. Reference experiences indirectly — let the user fill in the specifics themselves.
Confidence
85% confidence
Finding
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Instruction Override

High
Category
Prompt Injection
Content
def main():
    parser = argparse.ArgumentParser(description="Query audio generation status from xplai API")
    parser.add_argument("audio_id", type=str, help="Audio ID to query")
    parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode to print request/response details")

    args = parser.parse_args()
Confidence
70% confidence
Finding
This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Instruction Override

High
Category
Prompt Injection
Content
def main():
    parser = argparse.ArgumentParser(description="Query audio generation status from xplai API")
    parser.add_argument("audio_id", type=str, help="Audio ID to query")
    parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode to print request/response details")

    args = parser.parse_args()
Confidence
70% confidence
Finding
This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill uses sensitive capabilities—local file reads across conversation history and network transmission to a third-party API—but does not declare an explicit tool scope such as permissions or allowed-tools. That creates a governance gap: operators and users cannot reliably constrain or review what the skill is allowed to access, increasing the chance of overbroad data access or accidental exfiltration.

Session Persistence

Medium
Category
Rogue Agent
Content
2. **What tone fits their current state?** — High-stress → warm/slow. Curiosity → engaging/detailed. Boredom → surprising. Excitement → match energy. Post-achievement → celebratory then reflective.
3. **What depth and duration fit?** — Calibrate by cognition level (new vs. deep prior knowledge), available attention (late night → shorter, weekend → longer), and repetition tolerance (don't repeat what they already know).

**Custom Mode:** When no predefined mode fits, create a custom audio profile: name it descriptively (e.g., "赶完DDL后的温柔复盘"), define content structure based on inferred need, and set voice/pacing to match.

For the 9 predefined audio modes (Soul Healing, Daily Briefing, Knowledge Deep Dive, Content Digest, Bedtime Radio, Language Learning, Conversation Extension, Topic Tracker, Study Buddy), read `audio_modes.md` for triggers, durations, and suggestions.
Confidence
60% 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.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The file defines triggers such as "explain X to me," "what is X," "how does X work," and "summarize this," which are common conversational requests rather than narrowly scoped activation cues. Because the document says to use modes when a request "naturally fits" and to "blend freely," it does not provide clear constraints or negative examples to distinguish normal assistance from mode invocation.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The trigger list includes "help me practice," which is generic and could apply to many unrelated tasks, not just language learning. Without additional constraints on what kind of practice qualifies, the activation scope is ambiguous and may cause unintended invocation.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The script loads raw daily memory files and extracts profile fields from USER.md, then includes them in the JSON output that is intended to feed prompt composition for an external API workflow. That contradicts the stated privacy boundary in the skill description and creates a real data-exfiltration risk: sensitive local notes, memories, and profile attributes can be transmitted off-device if later prompt-building code includes this output.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The collector aggregates conversation fragments, daily memories, and user profile data into one output without any user-facing disclosure or consent mechanism in this code path. In the context of a skill that may send composed prompts to a third-party API, silent collection of personal history materially increases privacy risk and the chance of oversharing sensitive data.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
When DEBUG is enabled, the utility logs full request URLs and complete JSON/parameter bodies with no redaction. In this skill, prompts may contain highly sensitive personal context and the metadata states only the external API transmission is minimized; local debug logs could still expose that sensitive content to terminal history, log collectors, or shared environments.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
When DEBUG is enabled, the helper prints full response bodies, which may include generated audio request contents, user-derived summaries, API error details, tokens, or other sensitive data returned by the external service. Because this skill processes personal mental state, life events, and content digests, response logging increases confidentiality risk even if the remote API flow itself is otherwise constrained.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The document says to avoid 'precise surveillance,' but later directs use of structured profile fields and recurring conversation details in the prompt. That inconsistency creates a privacy footgun: implementers may believe the system is privacy-preserving while still exporting identifying or intimate context in summarized form.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The prompt template explicitly instructs inclusion of personal data elements such as name, MBTI, current state, related people/projects, and inferred needs in the final API prompt. Even if only the composed prompt is sent externally, that still constitutes transmission of sensitive or profiling-derived data, which conflicts with the safer manifest claims and can leak more context than necessary to a third-party service.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The file instructs the prompt builder to include MBTI as the fixed Chinese phrase "用户人格类型 XXXX" and says there is no need to infer if provided. This hard-codes a Chinese-language presentation rather than adapting to the user's preferred language, and no opt-in or justification is given.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The prompt length policy explicitly defines Chinese as the primary constrained format and pairs it with multiple instructions elsewhere to include Chinese labels and Mandarin-specific voice choices. Although L023 says to follow the user's language, L114-L115 and L133-L135 still prescribe Chinese output conventions without clarifying that they apply only when the user wants Chinese, which creates a locale policy concern.

Session Persistence

Medium
Category
Rogue Agent
Content
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
        debug_utils.debug_print(f"Audit log written to {AUDIT_LOG_PATH}")
    except OSError as e:
        debug_utils.debug_print(f"Failed to write audit log: {e}")


def has_persistent_consent():
Confidence
72% confidence
Finding
The code persists consent state to a local file, which creates session/state retention around a privacy-sensitive workflow involving local context sources and external API transmission. Persistent consent can weaken informed consent over time: future runs may send data without re-surfacing the boundary notice, especially if device ownership changes or the user forgets the prior approval.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The function will transmit whatever text it is given to the external xplai API after only heuristic redaction checks, without any technical control ensuring the text excludes local session, memory, or USER.md content. That creates a data-boundary mismatch with the skill description: if upstream code passes raw local context or overly broad composed prompts, sensitive local data can be exfiltrated despite the manifest’s assurances.

Natural-Language Policy Violations

Low
Confidence
67% confidence
Finding
The trigger examples for Soul Healing Mode include multiple Chinese phrases, but the document does not explain whether multilingual triggering is optional, illustrative, or user-selected. This can create an implicit language/locale preference in activation behavior without an explicit opt-in or stated language policy.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The docstring says only structured fields are extracted from USER.md, but the code also captures free-form notes/preferences. Free-form notes are much more likely to contain sensitive personal information, so this mismatch can cause developers and users to underestimate what data is being collected and later shared.

Natural-Language Policy Violations

Low
Confidence
70% confidence
Finding
The CLI description says it will collect personalized context from OpenClaw conversation history, even though the tool supports both qclaw and openclaw. This hard-coded wording imposes a specific context in natural language rather than presenting a neutral or user-selected option.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The consent notice says sensitive text will not be written to audit.log until explicit confirmation, but after --allow-sensitive the code can still write heuristic-sensitive prompt content in plaintext to the local audit log. While this is not remote exfiltration by itself, it can expose personal data locally to other users, backups, support bundles, or later compromise of the host.

Static analysis

No suspicious patterns detected.