T09 · Insecure Skill Coding Practices
- 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
Security audit
Security checks for vulnerabilities and agentic risk
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.
scripts/claude_ai.py:172Identity-Linked User Query History Is Retained Without Adequate Disclosure or Lifecycle Controls
bot/mapulse_bot.py:478Full Telegram Messages and Stable User IDs Are Written to Plaintext Logs
SKILL.md:59Unpinned Third-Party Dependencies Create a Mutable Supply-Chain Risk
scripts/run_briefing.sh:1Briefing Wrapper Imports Credentials from a Hard-Coded Deployment-Specific Environment File
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"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()
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
{"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 matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
#!/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 matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
#!/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 matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
#!/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
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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 matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
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)).
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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,
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
return True
try:
resp = requests.post(
f"https://api.telegram.org/bot{TOKEN}/sendMessage",
json={
"chat_id": chat_id,
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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 matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
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 =
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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,
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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,
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
return None
try:
resp = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
def call_llm(messages, model=MODEL_FAST, max_tokens=500, temperature=0.3):
"""OpenRouter API 호출"""
try:
resp = requests.post(
OPENROUTER_URL,
headers=HEADERS,
json={
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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)}
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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)}
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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 matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
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
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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,
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"1:1 AI 대화로 관심 종목 분석, 투자 전략 상담까지."
)
resp = requests.post(
f"https://api.telegram.org/bot{TOKEN}/sendMessage",
json={
"chat_id": channel_id,
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.
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.
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.
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.
No suspicious patterns detected.