Back to skill

Security audit

mapulse-korea

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches a Telegram market-analysis bot, but it under-discloses user message retention/logging and includes a wrapper that can load another deployment's secrets.

Install only after reviewing the privacy and deployment behavior. Use a dedicated virtual environment and unprivileged service account, pin dependencies, remove or fix run_briefing.sh before scheduling it, restrict logs, and disclose or disable storage of user message text and conversation history. Treat Telegram, LLM, OpenNews, and Twitter tokens as production secrets and confirm any cron job recipients before enabling broadcasts.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (4)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/claude_ai.py:172
Finding

Identity-Linked User Query History Is Retained Without Adequate Disclosure or Lifecycle Controls

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
bot/mapulse_bot.py:478
Finding

Full Telegram Messages and Stable User IDs Are Written to Plaintext Logs

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:59
Finding

Unpinned Third-Party Dependencies Create a Mutable Supply-Chain Risk

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/run_briefing.sh:1
Finding

Briefing Wrapper Imports Credentials from a Hard-Coded Deployment-Specific Environment File

Content
View full analysis
> /tmp/mapulse_briefing.log 2>&1 ``` ### Technical Analysis The wrapper unconditionally sources a fixed `.env` file under another deployment-specific absolute path and exports all variables from that file. It then executes a script from the same hard-coded installation. This behavior is inconsistent with `SKILL.md`, which states that `.env` files are not loaded automatically. It also prevents reliable environment isolation between installations. If the fixed path exists, invoking the wrapper can cause the current installation to use Telegram or API credentials belonging to the `mapulse-prod` deployment. The wrapper does not validate file ownership, permissions, or whether the resolved path belongs to the intended installation. No evidence was found that it transmits the entire environment or `.env` file to an attacker-controlled service; the risk is credential-scope confusion and unintended secret use. ### Attack Path 1. An operator invokes or schedules `scripts/run_briefing.sh`, assuming it uses the current project's configuration. 2. The script sources `/home/admin/.openclaw/workspace/skills/mapulse-prod/.env`. 3. Every assignment in that file is exported to the briefing process. 4. The wrapper runs the briefing script from the hard-coded production directory rather than the current project directory. 5. Messages may be sent using the production bot credentials and production database configuration. 6. If an attacker with prior local write access can modify the sourced file, shell commands placed in it would execute when the wrapper runs. The final step requires pre- ...[truncated 630 chars]
Remediation
View remediation
Vulnerability Patterns
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (162)

Tainted flow: 'payload' from os.environ.get (line 81, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/claude_ai.py (reported line 87)May include surrounding context.

python
"system": system_prompt,
                "messages": [{"role": "user", "content": user_message}],
            }
            resp = requests.post(config["url"], headers=config["headers"],
                                 json=payload, timeout=30)
            if resp.status_code == 200:
                data = resp.json()

Tainted flow: 'payload' from os.environ.get (line 98, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/claude_ai.py (reported line 106)May include surrounding context.

python
{"role": "user", "content": user_message},
                ],
            }
            resp = requests.post(config["url"], headers=config["headers"],
                                 json=payload, timeout=30)
            if resp.status_code == 200:
                data = resp.json()

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/cron_briefing.py (reported line 17)May include surrounding context.

python
#!/usr/bin/env python3
"""
Mapulse Cron — 매일 아침 시황 + 주요 뉴스 자동 push
KST 22:00 (UTC 13:00) 실행 — 미국 시장 개장 후, 사용자 취침 전 최적 타이밍

crontab: 0 13 * * 1-5 /usr/bin/python3 /path/to/cron_briefing.py
"""

import os
import sys
import json
import requests
import time

sys.path.insert(0, os.path.dirname(__file__))

TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
DB_PATH = os.environ.get("MAPULSE_DB", os.path.join(os.path.dirname(__file__), "..", "data", "mapulse.db"))


def get_all_users():
    """모든 활성 사용자"""
    import sqlite3
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    rows = conn.execute("SELECT user_id FROM users WHERE total_calls > 0").fetchall()
    conn.close()
    return [r["user_id"] for r in rows]


def send_message(chat_id, text):
    """텔레그램 메시지 전송"""
    try:
        resp = requests.post(
            f"https://api.telegram.org/bot{TOKE

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/cron_day2_survey.py (reported line 23)May include surrounding context.

python
#!/usr/bin/env python3
"""
Mapulse Cron — 매일 아침 시황 + 주요 뉴스 자동 push
KST 22:00 (UTC 13:00) 실행 — 미국 시장 개장 후, 사용자 취침 전 최적 타이밍

crontab: 0 13 * * 1-5 /usr/bin/python3 /path/to/cron_briefing.py
"""

import os
import sys
import json
import requests
import time

sys.path.insert(0, os.path.dirname(__file__))

TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
DB_PATH = os.environ.get("MAPULSE_DB", os.path.join(os.path.dirname(__file__), "..", "data", "mapulse.db"))


def get_all_users():
    """모든 활성 사용자"""
    import sqlite3
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    rows = conn.execute("SELECT user_id FROM users WHERE total_calls > 0").fetchall()
    conn.close()
    return [r["user_id"] for r in rows]


def send_message(chat_id, text):
    """텔레그램 메시지 전송"""
    try:
        resp = requests.post(
            f"https://api.telegram.org/bot{TOKE

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/cron_news_scan.py (reported line 18)May include surrounding context.

python
#!/usr/bin/env python3
"""
Mapulse Cron — 매일 아침 시황 + 주요 뉴스 자동 push
KST 22:00 (UTC 13:00) 실행 — 미국 시장 개장 후, 사용자 취침 전 최적 타이밍

crontab: 0 13 * * 1-5 /usr/bin/python3 /path/to/cron_briefing.py
"""

import os
import sys
import json
import requests
import time

sys.path.insert(0, os.path.dirname(__file__))

TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
DB_PATH = os.environ.get("MAPULSE_DB", os.path.join(os.path.dirname(__file__), "..", "data", "mapulse.db"))


def get_all_users():
    """모든 활성 사용자"""
    import sqlite3
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    rows = conn.execute("SELECT user_id FROM users WHERE total_calls > 0").fetchall()
    conn.close()
    return [r["user_id"] for r in rows]


def send_message(chat_id, text):
    """텔레그램 메시지 전송"""
    try:
        resp = requests.post(
            f"https://api.telegram.org/bot{TOKE

Tainted flow: 'TOKEN' from os.environ.get (line 17, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/cron_briefing.py (reported line 34)May include surrounding context.

python
def send_message(chat_id, text):
    """텔레그램 메시지 전송"""
    try:
        resp = requests.post(
            f"https://api.telegram.org/bot{TOKEN}/sendMessage",
            json={
                "chat_id": chat_id,

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/cron_daily_metrics.py (reported line 23)May include surrounding context.

python
v:
  TELEGRAM_BOT_TOKEN  — required
  MAPULSE_DB          — optional (default: data/mapulse.db)
  OPS_CHAT_IDS        — comma-separated Telegram user IDs to receive the report

crontab:
  0 8 * * * python3 cron_daily_metrics.py
"""

import os
import sys
import sqlite3
import requests
from datetime import datetime, timedelta, timezone

sys.path.insert(0, os.path.dirname(__file__))

DB_PATH = os.environ.get("MAPULSE_DB", os.path.join(os.path.dirname(__file__), "..", "data", "mapulse.db"))
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
OPS_CHAT_IDS = [x.strip() for x in os.environ.get("OPS_CHAT_IDS", "").split(",") if x.strip()]

KST = timezone(timedelta(hours=9))


def get_conn():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn


def collect_metrics():
    """Collect usage metrics."""
    conn = get_conn()
    today_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    yesterday_utc = (datetime.now(timezone.utc) - timedelta(days=1)).

Tainted flow: 'BOT_TOKEN' from os.environ.get (line 24, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/cron_daily_metrics.py (reported line 95)May include surrounding context.

python
for chat_id in OPS_CHAT_IDS:
        try:
            resp = requests.post(
                f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
                json={"chat_id": chat_id, "text": text, "parse_mode": "HTML"},
                timeout=15,

Tainted flow: 'TOKEN' from os.environ.get (line 23, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/cron_day2_survey.py (reported line 109)May include surrounding context.

python
return True

    try:
        resp = requests.post(
            f"https://api.telegram.org/bot{TOKEN}/sendMessage",
            json={
                "chat_id": chat_id,

Tainted flow: 'TOKEN' from os.environ.get (line 18, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/cron_news_scan.py (reported line 52)May include surrounding context.

python
def send_message(chat_id, text):
    try:
        resp = requests.post(
            f"https://api.telegram.org/bot{TOKEN}/sendMessage",
            json={
                "chat_id": chat_id,

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/cron_platform_push.py (reported line 37)May include surrounding context.

python
1-5 → KST 20:50 (evening)

주의: 사용자 개인 푸시와 독립적으로 운영 (push_type='platform_xxx')
"""

import os
import sys
import json
import time
import logging
import sqlite3
import requests

sys.path.insert(0, os.path.dirname(__file__))

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("platform_push")

TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
DB_PATH = os.environ.get("MAPULSE_DB", os.path.join(os.path.dirname(__file__), "..", "data", "mapulse.db"))

# Public channel for briefing (set after channel is created)
CHANNEL_ID = os.environ.get("MAPULSE_CHANNEL_ID", "")

# LLM for generating analysis
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
LLM_MODEL = "anthropic/claude-sonnet-4"


# ─── DB Helpers ───

def get_all_users():
    """모든 사용자 (한 번이라도 사용한 유저)"""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    rows =

Tainted flow: 'TOKEN' from os.environ.get (line 37, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/cron_platform_push.py (reported line 79)May include surrounding context.

python
logger.error("TELEGRAM_BOT_TOKEN not set")
        return False
    try:
        resp = requests.post(
            f"https://api.telegram.org/bot{TOKEN}/sendMessage",
            json={
                "chat_id": chat_id,

Tainted flow: 'TOKEN' from os.environ.get (line 37, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/cron_platform_push.py (reported line 96)May include surrounding context.

python
if "can't parse entities" in desc.lower() or "parse" in desc.lower():
                logger.warning(f"Markdown parse error for {chat_id}, retrying as plain text")
                plain = text.replace("*", "").replace("_", "").replace("`", "")
                resp2 = requests.post(
                    f"https://api.telegram.org/bot{TOKEN}/sendMessage",
                    json={
                        "chat_id": chat_id,

Tainted flow: 'OPENROUTER_API_KEY' from os.environ.get (line 44, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/cron_platform_push.py (reported line 307)May include surrounding context.

python
return None

    try:
        resp = requests.post(
            "https://openrouter.ai/api/v1/chat/completions",
            headers={
                "Authorization": f"Bearer {OPENROUTER_API_KEY}",

Tainted flow: 'HEADERS' from os.environ.get (line 32, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/llm.py (reported line 199)May include surrounding context.

python
def call_llm(messages, model=MODEL_FAST, max_tokens=500, temperature=0.3):
    """OpenRouter API 호출"""
    try:
        resp = requests.post(
            OPENROUTER_URL,
            headers=HEADERS,
            json={

Tainted flow: 'HEADERS' from os.environ.get (line 13, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/news_6551.py (reported line 36)May include surrounding context.

python
if not TOKEN:
        return {"error": "OPENNEWS_TOKEN not configured"}
    try:
        r = requests.post(f"{API_BASE}/{endpoint}", headers=HEADERS, json=data, timeout=10)
        return r.json()
    except Exception as e:
        return {"error": str(e)}

Tainted flow: 'HEADERS' from os.environ.get (line 13, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/twitter_6551.py (reported line 48)May include surrounding context.

python
if not TOKEN:
        return {"error": "OPENNEWS_TOKEN not configured"}
    try:
        r = requests.post(f"{API_BASE}/{endpoint}", headers=HEADERS, json=data, timeout=10)
        return r.json()
    except Exception as e:
        return {"error": str(e)}

Tainted flow: 'HEADERS' from os.environ.get (line 13, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/news_6551.py (reported line 48)May include surrounding context.

python
if not TOKEN:
        return {"error": "OPENNEWS_TOKEN not configured"}
    try:
        r = requests.get(f"{API_BASE}/{endpoint}", headers=HEADERS, timeout=10)
        return r.json()
    except Exception as e:
        return {"error": str(e)}

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · scripts/setup_channel.py (reported line 17)May include surrounding context.

python
Channel Setup Helper
频道创建后运行此脚本,设置频道简介并测试发送

使用:
  1. 手动在 Telegram 中创建频道 "Mapulse 한국주식 브리핑"
  2. 将 @Mapulse_bot 添加为频道管理员 (需要发消息权限)
  3. 获取频道 username (如 @mapulse_kr) 或 chat_id
  4. 运行: python3 setup_channel.py @mapulse_kr
"""

import os
import sys
import requests

TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")

def test_channel(channel_id):
    """测试向频道发送消息"""
    print(f"\n🔍 Testing channel: {channel_id}")
    
    # Get chat info
    resp = requests.get(
        f"https://api.telegram.org/bot{TOKEN}/getChat",
        params={"chat_id": channel_id},
        timeout=10,
    )
    result = resp.json()
    if result.get("ok"):
        chat = result["result"]
        print(f"  ✅ Channel found: {chat.get('title', '?')}")
        print(f"  📝 Type: {chat.get('type')}")
        print(f"  🔗 Username: @{chat.get('username', 'N/A')}")
    e

Tainted flow: 'TOKEN' from os.environ.get (line 17, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/setup_channel.py (reported line 24)May include surrounding context.

python
print(f"\n🔍 Testing channel: {channel_id}")
    
    # Get chat info
    resp = requests.get(
        f"https://api.telegram.org/bot{TOKEN}/getChat",
        params={"chat_id": channel_id},
        timeout=10,

Tainted flow: 'TOKEN' from os.environ.get (line 17, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/setup_channel.py (reported line 51)May include surrounding context.

python
"1:1 AI 대화로 관심 종목 분석, 투자 전략 상담까지."
    )
    
    resp = requests.post(
        f"https://api.telegram.org/bot{TOKEN}/sendMessage",
        json={
            "chat_id": channel_id,

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Access to Twitter/OpenTwitter data via 6551.io, including deleted tweets and follower data, is a sensitive capability that materially exceeds the stated Korean-stock bot purpose. Undeclared social-monitoring functions and extra credentials broaden surveillance, legal, and privacy risks and could be abused for profiling or manipulative market commentary.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Access to Twitter/OpenTwitter data via 6551.io, including deleted tweets and follower data, is a sensitive capability that materially exceeds the stated Korean-stock bot purpose. Undeclared social-monitoring functions and extra credentials broaden surveillance, legal, and privacy risks and could be abused for profiling or manipulative market commentary.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Access to Twitter/OpenTwitter data via 6551.io, including deleted tweets and follower data, is a sensitive capability that materially exceeds the stated Korean-stock bot purpose. Undeclared social-monitoring functions and extra credentials broaden surveillance, legal, and privacy risks and could be abused for profiling or manipulative market commentary.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Access to Twitter/OpenTwitter data via 6551.io, including deleted tweets and follower data, is a sensitive capability that materially exceeds the stated Korean-stock bot purpose. Undeclared social-monitoring functions and extra credentials broaden surveillance, legal, and privacy risks and could be abused for profiling or manipulative market commentary.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.