Back to skill

Security audit

Gemini Assistant

Security checks for vulnerabilities and agentic risk

Overview

This Gemini assistant is mostly purpose-aligned, but it can route broad WhatsApp text/audio inputs and caller-supplied local audio file paths to Gemini without enough scoping or consent controls.

Review before installing. Use this only where users knowingly opt into sending text and voice content to Google Gemini. Avoid connecting it to broad WhatsApp routing without confirmation, and do not expose raw audio_path or system_instruction fields to untrusted callers. The publisher should constrain audio file access to trusted upload storage and use safer temporary output handling.

Vulnerability Patterns
  • 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
  • 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
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/handler.py:71
Finding
Unrestricted Local Audio File Read and External Transmission## Vulnerability Details **File Location**: `scripts/handler.py`, lines 71–80; request propagation occurs at lines 137–143 and 180–185 **Vulnerability Type**: Arbitrary local file access through an unvalidated audio path **Risk Level**: High ### Vulnerable Code ```python elif audio_path: # Convert audio to PCM and send import librosa y, sr = librosa.load(audio_path, sr=SAMPLE_RATE_IN) # Send as realtime audio input await session.send_realtime_input( audio=types.Blob( data=y.tobytes(), mime_type=f"audio/pcm;rate={SAMPLE_RATE_IN}" ) ) ``` The path originates directly from request data: ```python audio_path = request_data.get("audio_path") system_instruction = request_data.get("system_instruction") result = asyncio.run(_process_with_gemini( audio_path=audio_path, text_input=text_input, system_instruction=system_instruction )) ``` ### Technical Analysis The caller-controlled `audio_path` is passed directly to `librosa.load()` without canonicalization, directory containment checks, ownership checks, symlink rejection, or validation that the file was uploaded by the current requester. The file is opened with the privileges of the skill process. If the service account can read a local audio-decodable file, an untrusted caller can potentially cause that file to be loaded. Its decoded contents are then sent to the external Gemini API through `session.send_realtime_input()`. This creates a confused-deputy condition: the caller can exercise the process's filesystem privileges even when the caller does not have direct access to the selected file. Successful exploitation requires the target file to exist, be readable by the skill process, and be decodable by the audio processing stack. ### Attack Path 1. An attacker reaches an integration that invokes `handle_request()` and permits control of `audio_path`. 2. The at ...[truncated 1076 chars]
Remediation
## Remediation Suggestions - Do not accept arbitrary filesystem paths from request data. Accept an opaque upload identifier and resolve it through trusted server-side metadata. - Store uploaded audio in a dedicated directory owned by the service and configured with restrictive permissions. - Resolve the candidate path with `Path.resolve()` and verify that it remains within the dedicated upload directory. - Reject absolute paths, traversal components, symbolic links, non-regular files, device files, sockets, and named pipes. - Open files using mechanisms that prevent symlink following, such as `O_NOFOLLOW` where supported, and verify the opened descriptor with `fstat()`. - Confirm that the file belongs to the current request or authenticated user before processing it. - Enforce strict maximum file sizes, processing timeouts, and accepted media types. Validate file content rather than relying only on its extension. - Run the skill under a dedicated, unprivileged operating-system account that cannot read unrelated application or user data. - Clearly disclose that accepted audio is transmitted to Google Gemini and apply the required retention and consent controls.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/handler.py:101
Finding
Predictable Shared Temporary Output Enables Symlink-Based File Overwrite## Vulnerability Details **File Location**: `scripts/handler.py`, lines 101–111 and 130–134 **Vulnerability Type**: Unsafe predictable temporary file creation **Risk Level**: Medium ### Vulnerable Code ```python result = subprocess.run( [ FFMPEG, "-i", wav_path, "-c:a", "libopus", "-b:a", "32k", "-ar", "48000", "-ac", "1", output_path, "-y" ], capture_output=True, timeout=30, env=env, ) ``` The destination is derived from a predictable, caller-influenced identifier and placed directly in shared `/tmp`: ```python chat_id = request_data.get("chat_id", "unknown") text_input = request_data.get("text") audio_path = request_data.get("audio_path") system_instruction = request_data.get("system_instruction") safe_id = str(chat_id).replace("@", "_").replace("+", "").replace(".", "_") voice_output_path = f"/tmp/gemini_voice_{safe_id}.ogg" ``` ### Technical Analysis The output filename is deterministic and is created in the globally shared `/tmp` directory. It is not allocated atomically with `tempfile`, and the code does not verify that the destination is a new regular file rather than an existing symbolic link. FFmpeg receives the `-y` option, which authorizes overwriting an existing output. A local attacker able to create files in `/tmp` can predict identifiers such as `cli`, pre-create the corresponding path as a symlink, and attempt to redirect FFmpeg's output to another file writable by the skill process. The `safe_id` transformation is also incomplete: it replaces only `@`, `+`, and `.`, while leaving path separators and other unsafe characters intact. Although the fixed filename prefix constrains some traversal forms, the identifier should not be treated as a safe path component. ### Attack Path 1. A local attacker identifies or predicts the `chat_id` used by a skill invocation. The CLI always uses the predictable value ...[truncated 1402 chars]
Remediation
## Remediation Suggestions - Create a private temporary directory for the service with mode `0700` instead of writing directly to shared `/tmp`. - Generate unpredictable output names with `tempfile.NamedTemporaryFile()` or `tempfile.mkstemp()` and use restrictive file permissions. - Create the destination atomically with exclusive-creation semantics so an existing file or symlink cannot be substituted. - Where practical, pass an already secured file descriptor to the encoder or encode into a securely created temporary file and atomically rename it afterward. - Reject symlinks and verify with `lstat()` or descriptor-based checks that the destination is a regular file owned by the expected account. - Do not use `-y` against a caller-derived or pre-existing destination. Fail safely if the destination already exists. - Do not embed `chat_id` directly in a filesystem path. Use a random identifier and maintain any chat-to-file association separately. - If an identifier must be represented in a filename, allow only a strict character set such as ASCII letters, digits, underscores, and hyphens, and enforce a short maximum length. - Run the process under a dedicated unprivileged account with narrowly scoped write permissions.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
Findings (14)

Missing User Warnings

High
Confidence
98% confidence
Finding
The documentation explains text and voice usage but does not warn that user prompts and audio recordings are transmitted to Google's Gemini API. This creates a meaningful privacy and data-governance risk because users may provide sensitive content under the assumption the skill operates locally or without third-party disclosure.

Credential Access

High
Category
Privilege Escalation
Content
from google import genai
from google.genai import types

# Load .env file manually if present
env_path = Path(__file__).parent / ".env"
if env_path.exists():
    with open(env_path) as f:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
from google.genai import types

# Load .env file manually if present
env_path = Path(__file__).parent / ".env"
if env_path.exists():
    with open(env_path) as f:
        for line in f:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
sf.write(wav_path, audio_np, SAMPLE_RATE_OUT, format="WAV", subtype="PCM_16")

    try:
        env = os.environ.copy()
        env["LD_LIBRARY_PATH"] = "/usr/lib/x86_64-linux-gnu"
        result = subprocess.run(
            [
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Ssd 1

High
Confidence
96% confidence
Finding
Untrusted callers can fully replace the system instruction via request_data, allowing them to override the assistant's default behavior and any safety boundaries encoded there. This enables prompt-injection-style policy bypass, potentially causing unsafe outputs, data mishandling, or behavior outside the skill's intended scope.

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Content
ice_output_path}"
        
        return response
        
    except Exception as e:
        error_msg = str(e)
        print(f"[gemini-assistant] Error: {error_msg}")
        import traceback
        traceback.print_exc()
        
        return {
            "message": f"Sorry, an error occurred: {str(e)}"
        }


def main():
    """CLI entry point."""
    parser = argparse.ArgumentParser(description="Gemini Assistant")
    parser.add_argument("input_text", nargs="?", help="Text input to send to Gemini")
    parser.add_argument("--audio", "-a", help="Path to audio file for voice input")
    parser.add_argument("--system", "-s", help="Custom system instruction")
    
    args = parser.parse_args()
    
    request_data = {
        "chat_id": "cli",
        "text": args.input_text,
        "audio_path": args.audio,
        "system_instruction": args.system
    }
    
    result = handle_request(request_data)
    print(json.dumps(result, indent=2, ensure_ascii=False))


if __name_
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Vague Triggers

High
Confidence
98% confidence
Finding
The trigger list includes very common words and phrases such as "ai," "help," "what is," "how to," "explain," and "tell me," which are likely to match ordinary conversation and cause unintended invocation. In a WhatsApp voice/audio/text context, this can capture unrelated user messages and route them to the skill unexpectedly, creating privacy, consent, and misuse risks even without overtly malicious code.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill advertises shell and environment-variable dependent behavior but does not declare any explicit tool scope or permissions boundary. That omission can cause the platform or user to invoke a broadly capable skill without clear visibility into what resources it may access, increasing the risk of unintended command execution or secret exposure.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The description uses broad, catch-all activation language for a general-purpose assistant, which makes it likely to be selected in many contexts beyond a narrowly intended use case. Over-broad routing increases the chance that sensitive prompts or tasks are sent to this external-model skill when a more specific or safer local skill should have been used.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code forwards caller-supplied audio to the Gemini API without any consent, disclosure, or visible privacy gating in this path. Because voice data can contain sensitive personal information, silent transmission to a third-party provider creates a real privacy and compliance risk.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The manifest describes a Gemini-based assistant with voice and text support, which justifies network calls to Gemini and audio handling. However, invoking a local subprocess via ffmpeg introduces host-level execution capability that is not stated in the manifest and is not an obvious requirement of being a general-purpose assistant.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
        env = os.environ.copy()
        env["LD_LIBRARY_PATH"] = "/usr/lib/x86_64-linux-gnu"
        result = subprocess.run(
            [
                FFMPEG, "-i", wav_path,
                "-c:a", "libopus",
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The manifest hard-codes the language setting to "en" with no indication that users can opt into another language or that the constraint is required for a region-specific purpose. This is a natural-language policy concern because it imposes a locale choice unilaterally.

Context-Inappropriate Capability

Low
Confidence
76% confidence
Finding
The code reads a local .env file and injects its contents into process environment variables before running. While reading an API key is needed for Gemini access, direct file-based secret loading is an additional local file access capability not described in the manifest and is not inherent to a general assistant's user-facing purpose.

Static analysis

No suspicious patterns detected.