Back to skill

Security audit

memory-assistant

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent memory and reminder assistant, but it should be reviewed because it stores personal reminders and item locations, sends spoken text to SenseAudio, and includes file-writing scripts with containment risks.

Install only if you are comfortable storing reminders and item locations locally and sending spoken reminder/location text to SenseAudio. Avoid putting highly sensitive locations, travel details, secrets, or medical/financial reminders into it unless you accept that cloud TTS exposure, and do not run the daemon or cron setup until you have reviewed the storage path and file-writing behavior.

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
scripts/run_reminders.py:139
Finding

Path Traversal Through an Unvalidated Reminder Identifier

Content
View full analysis

Vulnerability Details

File Location: scripts/run_reminders.py, lines 72–78 and 135–147
Vulnerability Type: Path traversal leading to an out-of-scope file write
Risk Level: Medium

Vulnerable Code

python
def tts_and_save(text: str, out_path: Path, api_key: str, voice_id: str = DEFAULT_VOICE) -> Path:
    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
    body = {
        "model": "SenseAudio-TTS-1.0",
        "text": text,
        "stream": False,
        "voice_setting": {"voice_id": voice_id},
    }
    r = requests.post(TTS_URL, headers=headers, json=body, timeout=30)
    r.raise_for_status()
    data = r.json()
    if data.get("base_resp", {}).get("status_code") != 0:
        raise RuntimeError(data.get("base_resp", {}).get("status_msg", "TTS failed"))
    hex_audio = data.get("data", {}).get("audio")
    if not hex_audio:
        raise RuntimeError("No audio in response")
    out_path.parent.mkdir(parents=True, exist_ok=True)
    with open(out_path, "wb") as f:
        f.write(bytes.fromhex(hex_audio))
    return out_path
python
for r in to_notify:
    event = r.get("event", "提醒")
    text = f"提醒:{event}"
    if dry_run:
        print(f"[dry-run] would speak: {text}", file=sys.stderr)
        continue
    out_path = data_dir / "audio" / f"reminder_{r.get('id', '')}.mp3"
    try:
        tts_and_save(text, out_path, api_key, voice_id)
        play_audio(out_path)
    except Exception as e:
        print(f"TTS/play failed for reminder {r.get('id')}: {e}", file=sys.stderr)
        continue
    r["status"] = "notified"

Technical Analysis

The reminder identifier is loaded from reminders.json and interpolated directly into an output path without validating its characters or checking the resolved destination. Path separators and parent-directory components in the identifier are therefore interpret ...[truncated 1937 chars]

Remediation
View remediation

Remediation Suggestions

  1. Generate reminder identifiers internally using UUIDs rather than accepting storage-derived identifiers as filenames.

  2. Enforce a strict allowlist before using an identifier, such as [A-Za-z0-9_-]+, with a reasonable maximum length.

  3. Construct and resolve the output path, then verify that it remains beneath the resolved audio directory:

    python
    import re
    from uuid import UUID
    
    reminder_id = str(r.get("id", ""))
    if not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", reminder_id):
        raise ValueError("Invalid reminder ID")
    
    audio_dir = (data_dir / "audio").resolve()
    audio_dir.mkdir(parents=True, exist_ok=True)
    out_path = (audio_dir / f"reminder_{reminder_id}.mp3").resolve()
    
    if out_path.parent != audio_dir:
        raise ValueError("Output path escapes audio directory")
    
  4. Where supported, use no-follow and exclusive file-opening controls to reduce symlink and overwrite risks.

  5. Validate the complete reminder schema when records are created and again when they are loaded from disk.

  6. Write reminder data and audio files using restrictive permissions appropriate for potentially sensitive reminder content.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/speak.py:139
Finding

Predictable Shared Temporary File Enables Symlink-Based File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/speak.py, lines 72–76 and 137–145
Vulnerability Type: Unsafe predictable temporary-file handling
Risk Level: Medium

Vulnerable Code

python
hex_audio = data.get("data", {}).get("audio")
if not hex_audio:
    raise RuntimeError("No audio in response")
out_path.parent.mkdir(parents=True, exist_ok=True)
with open(out_path, "wb") as f:
    f.write(bytes.fromhex(hex_audio))
return out_path
python
api_key = get_api_key()
out_path = args.out or Path(tempfile.gettempdir()) / "memory_assistant_tts.mp3"
tts_senseaudio(text, out_path, api_key, args.voice)
print(out_path.resolve(), file=sys.stderr if args.play else sys.stdout)

if args.play:
    play_audio(out_path)

Technical Analysis

When the user does not provide --out, the script always writes to the same predictable name in the system temporary directory:

text
<temporary-directory>/memory_assistant_tts.mp3

System temporary directories are commonly shared between local users. The script opens this path using ordinary Python file semantics with truncation enabled and without exclusive creation or a no-follow option. If the path already exists as a symbolic link, the write may follow that link.

Platform-level temporary-directory protections can mitigate some cross-user symlink attacks, but they are not universally available or consistently configured. The fixed name also causes concurrent Skill invocations to overwrite one another's output.

Attack Path

  1. A local attacker predicts the fixed temporary filename.
  2. Before the victim runs the script, the attacker creates that path as a symbolic link to a file writable by the victim account.
  3. The victim executes speak.py without specifying --out.
  4. SenseAudio returns synthesized audio.
  5. The script opens the predictable path with "wb", potentially follows the symbolic link, and truncates th ...[truncated 731 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace the fixed filename with a securely and atomically created unique temporary file:

    python
    if args.out is None:
        fd, generated_path = tempfile.mkstemp(
            prefix="memory_assistant_",
            suffix=".mp3",
        )
        os.close(fd)
        out_path = Path(generated_path)
    else:
        out_path = args.out
    
  2. Prefer NamedTemporaryFile(delete=False, suffix=".mp3") when compatible with the playback behavior of the target operating system.

  3. Create temporary files with restrictive permissions, such as owner read/write only.

  4. Where available, use O_NOFOLLOW to reject symbolic links and O_EXCL for new files.

  5. If user-provided --out paths are intentionally supported, document that they may overwrite existing files and consider requiring explicit confirmation or an overwrite flag.

  6. Delete automatically generated temporary audio after playback when retention is unnecessary.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (25)

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill sends reminder text and item-location content to an external TTS provider, but the description does not clearly warn users that this information leaves the local environment. Since the data may include sensitive personal schedules, meeting details, and locations of valuables such as keys or passports, this omission creates a meaningful privacy and data-handling risk.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill describes capabilities that require environment access, file reads/writes, network calls, and shell execution, but it declares no explicit tool scope or permission boundaries. This increases the risk that the runtime grants broader access than users or reviewers expect, especially because the skill handles persistent storage and external API calls.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger phrases include very common expressions like “记一下” and “提醒我,” which can appear in ordinary conversation and cause the skill to activate unintentionally. Because the skill stores user content and may later send reminder text or location data to an external TTS service, accidental invocation can lead to unintended persistence or disclosure of sensitive information.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

This is a real external data transmission path: the skill posts user-provided text to the SenseAudio API for voice generation. In context, the transmitted text can contain sensitive reminders, schedules, and the storage locations of important personal items, so the network behavior is security-relevant and not merely incidental.

Content

Scanner excerpt · SKILL.md (reported line 55)May include surrounding context.

md
所有需要“读出来”的提醒(如:到点提醒、查询到的位置)均通过 SenseAudio 文本转语音 API 合成。

- **接口**:`POST https://api.senseaudio.cn/v1/t2a_v2`
- **鉴权**:`Authorization: Bearer YOUR_API_KEY`
- **必填参数**:`model: "SenseAudio-TTS-1.0"`,`text`(要朗读的文案),`voice_setting.voice_id`(如 `male_0004_a`)
- **流式**:可设 `stream: false` 一次取回整段音频;响应中 `data.audio` 为 hex 编码,需解码为二进制后保存为 mp3/wav 或送入播放器。

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The natural-language description and all listed triggers are exclusively in Chinese, which implies a fixed language requirement with no documented user choice or opt-in. This can violate language/locale policy when the skill does not clearly offer multilingual support or explain a justified locale limitation.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger list contains common conversational phrases and everyday nouns such as '提醒我', '放哪儿/放在哪', and item names like '护照' and '备用钥匙'. This can cause the skill to activate unintentionally during ordinary conversation, leading to unsolicited capture of sensitive location/reminder data or unexpected voice reminder behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The entire skill example and invocation flow are written as Chinese-only interactions, including fixed trigger phrasing like "记一下" and responses such as "嘿 Gemini" examples, with no indication that users may choose another language. Under the policy, a skill that imposes a specific language without user opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documentation instructs sending reminder text to a third-party TTS API and using an API key, but it does not explicitly warn that user-provided reminder contents may leave the local device. Because reminder text can contain sensitive personal schedule or location information, this creates a privacy and data-handling risk if users are unaware of the external transfer.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The cURL example demonstrates sending reminder text and authorization credentials to an external TTS endpoint. As documented behavior for a memory/reminder assistant, this can expose private schedule content to a third party if users or implementers follow the example without understanding the privacy implications.

Content

Scanner excerpt · reference.md (reported line 102)May include surrounding context.

更多见:https://senseaudio.cn/docs/voice_api。

cURL 示例(同步,保存为 mp3)

bash
# 1. 请求并保存 JSON

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

This example shows a concrete external transmission of text payloads and an authorization bearer token to SenseAudio. In a skill designed to store memories and reminders, the transmitted content can easily contain personally sensitive data, making the network call materially security-relevant.

Content

Scanner excerpt · reference.md (reported line 106)May include surrounding context.

bash
# 1. 请求并保存 JSON
curl -X POST https://api.senseaudio.cn/v1/t2a_v2 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

The example code performs an outbound POST request that transmits arbitrary reminder text to an external service. In this skill's context, the text may encode sensitive personal reminders, meeting details, or item-location information, so the transmission is security-relevant even though it appears to be intended functionality.

Content

Scanner excerpt · reference.md (reported line 135)May include surrounding context.

md
"stream": False,
        "voice_setting": {"voice_id": "male_0004_a"}
    }
    r = requests.post(url, headers=headers, json=body)
    r.raise_for_status()
    data = r.json()
    if data.get("base_resp", {}).get("status_code") != 0:

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · reference.md (reported line 53)May include surrounding context.

md
print("Error: pip install requests", file=sys.stderr)
    sys.exit(1)

TTS_URL = "https://api.senseaudio.cn/v1/t2a_v2"
DEFAULT_VOICE = "male_0004_a"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · reference.md (reported line 127)May include surrounding context.

md
print("Error: pip install requests", file=sys.stderr)
    sys.exit(1)

TTS_URL = "https://api.senseaudio.cn/v1/t2a_v2"
DEFAULT_VOICE = "male_0004_a"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/run_reminders.py (reported line 28)May include surrounding context.

python
print("Error: pip install requests", file=sys.stderr)
    sys.exit(1)

TTS_URL = "https://api.senseaudio.cn/v1/t2a_v2"
DEFAULT_VOICE = "male_0004_a"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/speak.py (reported line 33)May include surrounding context.

python
print("Error: pip install requests", file=sys.stderr)
    sys.exit(1)

TTS_URL = "https://api.senseaudio.cn/v1/t2a_v2"
DEFAULT_VOICE = "male_0004_a"

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

The script sends reminder text to an external TTS provider, which can expose potentially sensitive personal data such as item locations, schedules, or other spoken reminder contents. In the context of a memory/reminder assistant, that data is especially privacy-sensitive because reminders may contain secrets about a user's whereabouts, possessions, or routine.

Content

Scanner excerpt · scripts/run_reminders.py (reported line 72)May include surrounding context.

python
"stream": False,
        "voice_setting": {"voice_id": voice_id},
    }
    r = requests.post(TTS_URL, headers=headers, json=body, timeout=30)
    r.raise_for_status()
    data = r.json()
    if data.get("base_resp", {}).get("status_code") != 0:

Tainted flow: 'out_path' from requests.post (line 144, network input) → open (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/run_reminders.py (reported line 81)May include surrounding context.

python
if not hex_audio:
        raise RuntimeError("No audio in response")
    out_path.parent.mkdir(parents=True, exist_ok=True)
    with open(out_path, "wb") as f:
        f.write(bytes.fromhex(hex_audio))
    return out_path

External Transmission

Medium
Category
Data Exfiltration
Confidence
96% confidence
Finding

This network call transmits user-provided text or derived item-location phrases to an external service endpoint. Given the skill’s purpose—remembering object locations and reminders—the data can reveal sensitive behavioral patterns, schedules, and where important items are kept, so external transmission meaningfully increases privacy exposure.

Content

Scanner excerpt · scripts/speak.py (reported line 63)May include surrounding context.

python
"stream": False,
        "voice_setting": {"voice_id": voice_id},
    }
    r = requests.post(TTS_URL, headers=headers, json=body, timeout=30)
    r.raise_for_status()
    data = r.json()
    if data.get("base_resp", {}).get("status_code") != 0:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script sends arbitrary reminder text and item-location content to a third-party TTS API, and this content can contain sensitive personal information such as where valuables are stored or private schedule details. In the context of a memory/reminder assistant, that makes the privacy risk more significant because the transmitted data is inherently personal and the network transfer happens without an explicit consent or warning mechanism at use time.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/run_reminders.py (reported line 92)May include surrounding context.

python
path = str(filepath.resolve())
    system = platform.system()
    if system == "Darwin":
        subprocess.run(["afplay", path], check=True)
    elif system == "Windows":
        os.startfile(path)
    elif system == "Linux":

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/speak.py (reported line 84)May include surrounding context.

python
path = str(filepath.resolve())
    system = platform.system()
    if system == "Darwin":
        subprocess.run(["afplay", path], check=True)
    elif system == "Windows":
        os.startfile(path)
    elif system == "Linux":

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/run_reminders.py (reported line 98)May include surrounding context.

python
elif system == "Linux":
        for cmd in (["paplay", path], ["aplay", path], ["ffplay", "-nodisp", "-autoexit", path]):
            try:
                subprocess.run(cmd, check=True, capture_output=True)
                return
            except (FileNotFoundError, subprocess.CalledProcessError):
                continue

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/speak.py (reported line 90)May include surrounding context.

python
elif system == "Linux":
        for cmd in (["paplay", path], ["aplay", path], ["ffplay", "-nodisp", "-autoexit", path]):
            try:
                subprocess.run(cmd, check=True, capture_output=True)
                return
            except (FileNotFoundError, subprocess.CalledProcessError):
                continue

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The file consistently presents the skill instructions and usage in Chinese, and there is no indication that the user can choose another language or that the locale restriction is intentional and justified. This can violate language/locale policy when a specific language is effectively forced without opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The natural-language description, usage instructions, option descriptions, and user-facing argparse/help text are written in Chinese only. That forces a specific language experience without indicating user choice, which matches the language/locale policy concern for natural-language content.

Content

No source excerpt is available for this finding.