Back to skill

Security audit

Telegram - Conversa por Áudio (PICOCLAW)

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent Telegram voice-processing purpose, but its background file watcher is under-scoped and can process local files from shared directories and send voice content to external services.

Install only after reviewing the watcher design. Run it as an unprivileged account, avoid shared /tmp inputs, reject symlinks, set file-size and retention limits, disclose Groq and Edge TTS data handling to users, and avoid passing API keys on command lines.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/semi_auto_watcher.py:91
Finding
Symlink Following in Shared Media Directory Enables Arbitrary File Disclosure## Vulnerability Details **File Location**: `scripts/semi_auto_watcher.py:91-104`; `scripts/telegram_media_watcher.py:61-67,93-108` **Vulnerability Type**: Untrusted file processing and symbolic-link traversal **Risk Level**: High ### Vulnerable Code `scripts/semi_auto_watcher.py:91-104`: ```python def process(path: Path, state: dict): if path.suffix.lower() not in SUPPORTED: return if not stable(path): return digest = file_hash(path) if digest in state.get('processed', {}): return ts = int(time.time()) transcription = transcribe(path) stem = f'{path.stem}-{ts}' archived = DONE / f'{stem}{path.suffix.lower()}' shutil.copy2(path, archived) ``` `scripts/telegram_media_watcher.py:61-67`: ```python def process(path: Path): result = subprocess.run( ['python3', str(RUNNER), 'process', str(path), '--chat-id', CHAT_ID], capture_output=True, text=True, env=os.environ.copy(), ) return result.returncode, result.stdout, result.stderr ``` `scripts/telegram_media_watcher.py:93-108`: ```python for path in sorted(MEDIA_DIR.glob('*')): if not path.is_file(): continue if path.suffix.lower() not in SUPPORTED: continue if not stable(path): continue digest = file_hash(path) if digest in state.get('processed', {}): continue code, out, err = process(path) meta = {'file': str(path), 'hash': digest, 'time': int(time.time()), 'code': code, 'stdout': out, 'stderr': err} if code == 0: parsed = parse_sections(out) meta.update(parsed) ``` ### Technical Analysis Both watchers process files found under the shared temporary directory `/tmp/picoclaw_media`. The validation checks only whether an entry appears to be a file, has a supported filename suffix, and has a stable size. `Path.is_file()`, `Path.s ...[truncated 2424 chars]
Remediation
## Remediation Suggestions 1. Reject symbolic links before processing: ```python st = path.lstat() if stat.S_ISLNK(st.st_mode) or not stat.S_ISREG(st.st_mode): return ``` 2. Open files through a file descriptor using `os.open()` with `O_NOFOLLOW`, where supported, and perform `fstat()` on that descriptor. 3. Resolve the candidate path and verify that it remains strictly beneath the trusted media directory: ```python media_root = MEDIA_DIR.resolve(strict=True) candidate = path.resolve(strict=True) if candidate.parent != media_root: return ``` This containment check should supplement, not replace, `O_NOFOLLOW`. 4. Verify that the file is owned by the expected Picoclaw runtime account and is not writable by untrusted users. 5. Use a private directory with restrictive permissions instead of a generally accessible `/tmp` location. 6. Accept only files referenced by authenticated Telegram message metadata rather than processing every matching directory entry. 7. Enforce strict file-size, duration, and processing-time limits before hashing, copying, or uploading content. 8. Copy trusted input into a private directory through a safely opened descriptor and process that immutable copy. 9. Run the watcher as a dedicated, unprivileged service account with access only to the required media and state directories.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/finalize_reply.py:20
Finding
Caller-Controlled Metadata Path Allows Arbitrary JSON File Overwrite## Vulnerability Details **File Location**: `scripts/finalize_reply.py:20-40` **Vulnerability Type**: Unrestricted path access and arbitrary file modification **Risk Level**: Medium ### Vulnerable Code ```python def main(): if len(sys.argv) < 3: fail('Uso: finalize_reply.py <meta.json> <texto da resposta>') meta_path = Path(sys.argv[1]) reply = ' '.join(sys.argv[2:]).strip() if not meta_path.exists(): fail(f'Meta não encontrado: {meta_path}') if not reply: fail('Resposta vazia') meta = json.loads(meta_path.read_text()) SENT.mkdir(parents=True, exist_ok=True) ts = int(time.time()) out = SENT / f"{meta_path.stem}-{ts}.mp3" r = subprocess.run(['python3', str(GENERATE), reply, str(out)], capture_output=True, text=True, env=os.environ.copy()) if r.returncode != 0: fail(r.stderr.strip() or r.stdout.strip() or 'falha ao gerar áudio') meta['reply'] = reply meta['reply_audio'] = str(out) meta['status'] = 'ready_to_send' meta['finalized_at'] = ts meta_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2)) ``` ### Technical Analysis `finalize_reply.py` accepts the metadata filename directly from `sys.argv[1]`. Although the script defines a `PENDING` directory, it never verifies that the supplied path belongs to that directory. The script reads any existing path containing a valid JSON object, adds or replaces several properties, and writes the resulting JSON back to the same location. `Path.exists()`, `read_text()`, and `write_text()` also follow symbolic links. Therefore, the target can be: - An arbitrary JSON file outside the pending directory. - A relative path containing traversal components. - A symbolic link to another writable JSON file. Successful exploitation requires the target to contain a JSON object because the subsequent string-key assignments operate on a dictionary. ...[truncated 1712 chars]
Remediation
## Remediation Suggestions 1. Accept an opaque pending-record identifier rather than an arbitrary filesystem path. 2. Construct the metadata path internally beneath `PENDING`. 3. Resolve and verify path containment before reading: ```python pending_root = PENDING.resolve(strict=True) meta_path = (pending_root / f"{pending_id}.json").resolve(strict=True) if meta_path.parent != pending_root: fail("Invalid pending identifier") ``` 4. Reject symbolic links and non-regular files with `lstat()`. 5. Validate that the filename follows the expected format and has a `.json` suffix. 6. Validate the loaded JSON against a strict schema, including expected status, hash, timestamps, and archive path. 7. Use atomic replacement: write to a private temporary file, flush and `fsync()` it, then replace the destination. 8. Set restrictive directory and file permissions and run the script as a dedicated unprivileged account. 9. Prevent duplicate finalization by requiring `status == "pending_reply"` and applying an atomic state transition or file lock.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/transcribe_audio.py:23
Finding
Groq API Key Accepted Through Command-Line Arguments## Vulnerability Details **File Location**: `scripts/transcribe_audio.py:23-28` **Vulnerability Type**: Sensitive credential exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```python if __name__ == "__main__": if len(sys.argv) < 3: print("Uso: transcribe_audio.py <caminho_audio> <groq_api_key>") sys.exit(1) audio_path = sys.argv[1] api_key = sys.argv[2] ``` ### Technical Analysis The standalone transcription interface requires the Groq API key as a command-line argument. Process arguments are not an appropriate secret-transport mechanism. Depending on operating-system policy and deployment tooling, arguments may be exposed through: - Process-listing and process-inspection interfaces. - `/proc` process metadata. - Shell command history. - Service-manager diagnostics. - Monitoring, audit, and endpoint telemetry. - Wrapper-script logs and error reports. The watcher and runner already retrieve `GROQ_API_KEY` from the environment, so the standalone interface unnecessarily introduces a less secure alternative. ### Attack Path 1. An operator follows the command-line interface and starts: ```bash python3 scripts/transcribe_audio.py recording.ogg GROQ_SECRET_VALUE ``` 2. While the process is active, another local user or monitoring component with sufficient process-inspection permission obtains its command line. 3. Alternatively, the full invocation remains in shell history, automation logs, or operational telemetry. 4. The observer extracts the Groq API key. 5. The exposed credential is used to submit unauthorized API requests until it is revoked or expires. ### Impact Assessment Disclosure of the API key may permit unauthorized use of the associated Groq account within the permissions and quotas granted to that credential. Consequences can include: - Consumption of API quota and financial charges. - Service di ...[truncated 389 chars]
Remediation
## Remediation Suggestions 1. Remove API-key handling from positional command-line arguments. 2. Read the credential exclusively from `GROQ_API_KEY` or a protected secret-management service: ```python api_key = os.environ.get("GROQ_API_KEY") if not api_key: print("GROQ_API_KEY is not configured", file=sys.stderr) sys.exit(1) ``` 3. Update usage documentation so it never instructs users to place secrets on a command line. 4. Prefer service-manager credential facilities, encrypted secret stores, or file-based secrets with restrictive permissions for production deployment. 5. Ensure exceptions and logs never include the credential. 6. Rotate any API key that has previously been supplied through this interface and review shell history and telemetry retention for residual copies. 7. Scope the replacement key to the minimum required Groq capabilities and configure quota or billing alerts.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (36)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Describing a command-line local audio processor as a native Telegram voice pipeline misrepresents the actual trust model and could cause unsafe reliance on the skill. In practice, users may provide sensitive recordings believing they stay within Telegram-related handling when they are instead processed through local and external service paths not clearly disclosed.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Describing a command-line local audio processor as a native Telegram voice pipeline misrepresents the actual trust model and could cause unsafe reliance on the skill. In practice, users may provide sensitive recordings believing they stay within Telegram-related handling when they are instead processed through local and external service paths not clearly disclosed.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
Describing a command-line local audio processor as a native Telegram voice pipeline misrepresents the actual trust model and could cause unsafe reliance on the skill. In practice, users may provide sensitive recordings believing they stay within Telegram-related handling when they are instead processed through local and external service paths not clearly disclosed.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
Describing a command-line local audio processor as a native Telegram voice pipeline misrepresents the actual trust model and could cause unsafe reliance on the skill. In practice, users may provide sensitive recordings believing they stay within Telegram-related handling when they are instead processed through local and external service paths not clearly disclosed.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Describing a command-line local audio processor as a native Telegram voice pipeline misrepresents the actual trust model and could cause unsafe reliance on the skill. In practice, users may provide sensitive recordings believing they stay within Telegram-related handling when they are instead processed through local and external service paths not clearly disclosed.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
['python3', str(RUNNER), 'process', str(path)],
        capture_output=True,
        text=True,
        env=os.environ.copy(),
    )
    if result.returncode != 0:
        raise RuntimeError(result.stderr.strip() or result.stdout.strip() or 'erro desconhecido')
Confidence
82% confidence
Finding
Passing os.environ.copy() to a child process forwards all parent environment variables, which may include secrets such as API tokens, proxy credentials, or cloud keys. In this skill context, the child runner processes untrusted user-supplied audio, so any compromise, unsafe dependency, or logging inside runner.py would gain access to a larger secret set than necessary.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
SENT.mkdir(parents=True, exist_ok=True)
    ts = int(time.time())
    out = SENT / f"{meta_path.stem}-{ts}.mp3"
    r = subprocess.run(['python3', str(GENERATE), reply, str(out)], capture_output=True, text=True, env=os.environ.copy())
    if r.returncode != 0:
        fail(r.stderr.strip() or r.stdout.strip() or 'falha ao gerar áudio')
    meta['reply'] = reply
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.

Unvalidated Output Injection

High
Category
Output Handling
Content
SENT.mkdir(parents=True, exist_ok=True)
    ts = int(time.time())
    out = SENT / f"{meta_path.stem}-{ts}.mp3"
    r = subprocess.run(['python3', str(GENERATE), reply, str(out)], capture_output=True, text=True, env=os.environ.copy())
    if r.returncode != 0:
        fail(r.stderr.strip() or r.stdout.strip() or 'falha ao gerar áudio')
    meta['reply'] = reply
Confidence
95% confidence
Finding
Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
['python3', str(RUNNER), 'process', str(path), '--chat-id', CHAT_ID],
        capture_output=True,
        text=True,
        env=os.environ.copy(),
    )
    return result.returncode, result.stdout, result.stderr
Confidence
93% confidence
Finding
Passing os.environ.copy() to the subprocess forwards all available environment variables, potentially including API tokens, credentials, and internal configuration unrelated to audio processing. In this skill context, that is more dangerous because the runner handles untrusted media-triggered work, so any compromise or bug in the downstream process gains access to a broad set of secrets.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill documentation advertises behavior that relies on environment access, filesystem reads/writes, and shell execution, but it does not declare any tool scope or permission boundaries. This creates an authorization gap where an agent may invoke powerful capabilities without explicit review, increasing the chance of unintended file access, process spawning, or use of secrets such as API keys.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill notes the need for a GROQ API key but does not clearly warn that user audio content is transmitted to an external transcription provider. This is a privacy and compliance risk because users and operators may process sensitive voice data without informed consent, contractual review, or data-handling approval.

Session Persistence

Medium
Category
Rogue Agent
Content
### 1. Iniciar o Watcher (Semiautomático)
O watcher roda em background para processar novos áudios:
```bash
nohup python3 /root/.picoclaw/workspace/skills/telegram-native-audio/scripts/semi_auto_watcher.py > /root/.picoclaw/workspace/skills/telegram-native-audio/scripts/semi_auto_watcher.log 2>&1 &
```

### 2. Responder a uma pendência de áudio
Confidence
90% confidence
Finding
Running the watcher with 'nohup' in the background creates a persistent process outside normal interactive control and can continuously monitor directories, access new files, and accumulate logs. Persistent unattended execution raises the risk of uncontrolled resource use, unnoticed failures, prolonged access to sensitive media, and difficulty in auditing or stopping the process.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
The code executes another script via subprocess using the provided file path and inherited environment. While subprocess use may be part of the skill's implementation, this file itself provides no docstring, comment, or nearby disclosure explaining that uploaded audio is handed off to an external process.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_one(path: Path):
    result = subprocess.run(
        ['python3', str(RUNNER), 'process', str(path)],
        capture_output=True,
        text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code performs several persistent file operations: copying generated audio into the outbox, writing a metadata file, and moving the original input into a processed archive. Although there is logging after success, there is no warning, prompt, comment, or docstring disclosing these data-affecting behaviors before they occur.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
SENT.mkdir(parents=True, exist_ok=True)
    ts = int(time.time())
    out = SENT / f"{meta_path.stem}-{ts}.mp3"
    r = subprocess.run(['python3', str(GENERATE), reply, str(out)], capture_output=True, text=True, env=os.environ.copy())
    if r.returncode != 0:
        fail(r.stderr.strip() or r.stdout.strip() or 'falha ao gerar áudio')
    meta['reply'] = reply
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'meta' from pathlib.Path.read_text (line 29, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
meta['reply_audio'] = str(out)
    meta['status'] = 'ready_to_send'
    meta['finalized_at'] = ts
    meta_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2))
    print(json.dumps({'status': 'ready_to_send', 'meta_file': str(meta_path), 'audio_file': str(out), 'chat_id': meta.get('chat_id')}, ensure_ascii=False))
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.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script sends arbitrary input text to the external Edge TTS service, which can expose sensitive user content over the network without any consent flow, warning, or data-handling notice. In this skill's Telegram voice-processing context, users may submit private conversational content, so silent transmission to a third party creates a real confidentiality and privacy risk.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module docstring presents all user-facing usage and behavior in Portuguese, and the generated response/voice are hard-coded for pt-BR. There is no indication that the user can choose another language or that this locale restriction is intentionally justified as a region-specific tool, which matches the language/locale policy violation criteria.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The code copies received audio into an archive directory, creating persistent storage of voice messages that may contain sensitive or regulated data. Because this skill is specifically designed to process Telegram audio natively and continuously, silent archival materially increases privacy exposure if the host is shared, compromised, or backed up.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The watcher persists full audio transcriptions to disk in a pending metadata JSON file, which can expose sensitive spoken content such as personal data, credentials, or private conversations to other local processes, backups, or operators. In this Telegram voice-processing context, users are likely to assume ephemeral message handling, so undisclosed retention increases privacy and data-protection risk.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def process(path: Path):
    result = subprocess.run(
        ['python3', str(RUNNER), 'process', str(path), '--chat-id', CHAT_ID],
        capture_output=True,
        text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The watcher invokes a general Python runner with broad inherited privileges and environment, which is more capability than the narrow polling task requires. In a skill ecosystem where scripts may be modified or replaced, this creates a wider abuse path for secret access and unintended actions if the runner is compromised or behaves unexpectedly.

Tainted flow: 'CHAT_ID' from os.environ.get (line 16, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def process(path: Path):
    result = subprocess.run(
        ['python3', str(RUNNER), 'process', str(path), '--chat-id', CHAT_ID],
        capture_output=True,
        text=True,
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code stores stdout/stderr and parsed transcription/reply content in a persistent state file, which can contain sensitive voice-derived personal data and operational errors. Because Telegram audio often contains private conversations, retaining this data on disk increases exposure to local compromise, backup leakage, and accidental disclosure.

Static analysis

No suspicious patterns detected.