T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/watcher.py:89- Finding
Unauthenticated Relay Can Exfiltrate Private Agent Memory to an Attacker-Controlled Callback
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This KakaoTalk agent is coherent for a chatbot, but its relay/local modes expose private memory, callbacks, credentials, and a persistent local service with insufficient safeguards.
Install only after reviewing Relay mode carefully. Use a dedicated low-privilege account or machine, set webhook authentication, restrict callback destinations to Kakao-controlled HTTPS hosts, avoid sending MEMORY.md to external LLMs unless explicitly intended, rotate any secrets written into a plist, and avoid enabling the launchd service until uninstall and log-retention controls are clear.
scripts/watcher.py:89Unauthenticated Relay Can Exfiltrate Private Agent Memory to an Attacker-Controlled Callback
scripts/server.py:279Optional Authentication and Unrestricted Callback URL Enable Blind SSRF
scripts/install-service.sh:47Installer Registers a Cross-Session Auto-Starting launchd Service
scripts/install-service.sh:13Gemini API Key and Callback Secret Are Duplicated into a World-Readable Plist
scripts/server.py:102Webhook Messages and User Identifiers Are Persistently Logged Without Retention or Permission Controls
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=5) as resp:
return resp.status in (200, 201)
except Exception as e:
print(f"DB Error: {e}")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req, timeout=9) as resp:
data = json.load(resp)
return data["candidates"][0]["content"]["parts"][0]["text"].strip()
except Exception as e:
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method="POST",
)
with urllib.request.urlopen(req, timeout=OLLAMA_TIMEOUT) as resp:
data = json.load(resp)
text = data["message"]["content"].strip()
return _strip_thinking_tags(text)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method="POST",
)
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.load(resp)
return data["candidates"][0]["content"]["parts"][0]["text"].strip()
The server blindly POSTs to callbackUrl taken from the inbound request body, with no allowlist or origin validation. If signature verification is disabled or bypassed, an attacker can supply an arbitrary URL and coerce the server into making outbound requests, creating an SSRF primitive and potential internal network reachability.
headers={"Content-Type": "application/json; charset=utf-8"},
method="POST",
)
with urllib.request.urlopen(req, timeout=10) as resp:
_log(f"📤 콜백 전송 완료: status={resp.status}, url={callback_url[:60]}")
except Exception as e:
_log(f"❌ 콜백 전송 실패: {e}")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"Authorization": f"Bearer {SUPABASE_KEY}"
})
try:
with urllib.request.urlopen(req) as resp:
return json.load(resp)
except Exception as e:
print(f"Poll Error: {e}")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"Content-Type": "application/json"
})
try:
with urllib.request.urlopen(req):
pass
except Exception as e:
print(f"Update Error: {e}")
The code POSTs agent responses to a callback URL taken from queued message data without validating the destination. Because responses may contain model output influenced by local memory, this creates an SSRF/exfiltration path where an attacker who controls callback_url can receive sensitive data or force requests to arbitrary internal or external endpoints.
"Content-Type": "application/json"
})
try:
with urllib.request.urlopen(req) as resp:
print(f"✅ 발송 성공: {text[:20]}...")
return True
except Exception as e:
This call transmits user messages together with injected local MEMORY.md contents to an external Gemini API. That creates a real data exfiltration risk because private local context is sent off-host and may later be reflected back in responses, exceeding what a user would expect from a messaging-channel watcher.
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"}, method="POST")
try:
with urllib.request.urlopen(req) as resp:
data = json.load(resp)
return data["candidates"][0]["content"]["parts"][0]["text"].strip()
except Exception as e:
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
# 기본 대화 테스트
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
}' | python3 -m json.tool
# 세션 초기화
curl -s -X POST http://localhost:8401/kakao \
-H "Content-Type: application/json" \
-d '{
"userRequest": {"utterance": "처음으로", "user": {"id": "test_user_001"}},
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
}' | python3 -m json.tool
# "다시 물어보기" (LLM 타임아웃 후 재조회)
curl -s -X POST http://localhost:8401/kakao \
-H "Content-Type: application/json" \
-d '{
"userRequest": {"utterance": "다시 물어보기", "user": {"id": "test_user_001"}},
The script accesses a .env file expected to contain credentials, then later extracts and uses those secrets for service configuration. In this skill's context, the credentials include an API key and callback secret, so local disclosure could enable unauthorized API usage or forged webhook requests depending on downstream validation.
LOG_FILE="$LOG_DIR/kakaotalk.log"
ERR_FILE="$LOG_DIR/kakaotalk.err.log"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"
ENV_FILE="$HOME/.openclaw/.env"
# ─── 환경변수 로드 ─────────────────────────────────────────────────────────────
if [[ -f "$ENV_FILE" ]]; then
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
PUBLIC_URL=""
for i in $(seq 1 20); do
sleep 0.5
PUBLIC_URL=$(curl -sf http://127.0.0.1:4040/api/tunnels 2>/dev/null \
| python3 -c "
import sys, json
try:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# ─── 설정 ─────────────────────────────────────────────────────────────────────
# .env 로드 (간이)
def load_env():
env_path = os.path.expanduser("~/.openclaw/.env")
if os.path.exists(env_path):
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# ─── 설정 ─────────────────────────────────────────────────────────────────────
# .env 로드 (간이)
def load_env():
env_path = os.path.expanduser("~/.openclaw/.env")
if os.path.exists(env_path):
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# .env 로드 (간이)
def load_env():
env_path = os.path.expanduser("~/.openclaw/.env")
if os.path.exists(env_path):
with open(env_path) as f:
for line in f:
User messages and local memory are forwarded to an external LLM API with no visible disclosure or consent flow. That is dangerous because private operator data may leave the host and user content may be processed by a third party unexpectedly, with additional risk of the model echoing memory back in replies.
The code sends user messages and local memory contents to Google's Gemini API, creating third-party disclosure of potentially sensitive local context. Given the skill description, optional local access does not imply silent export of that data to an external model provider, so the capability is more dangerous in this context.
The skill advertises and instructs use of capabilities including environment variables, shell commands, network deployment, and local file interaction, but it does not declare an explicit permission or allowed-tools scope. That creates a trust and review gap: operators may install or run it without understanding the effective power it requires, especially because Relay mode bridges remote chat input to a local machine.
The description explicitly says the skill can connect to a local computer for file and memory access, but it does not provide a clear upfront warning about privacy, data exposure, or system-impact risks. Because the feature is exposed through a messaging channel, users may underestimate that chat requests could reach sensitive local resources.
The documentation markets a feature where KakaoTalk messages can request reading files from the user's computer, yet it gives no explicit safety constraints, authorization model, or warning about sensitive data access. In context, this is more dangerous because a remote messaging interface is being tied to a local watcher, which can turn ordinary chat prompts into access to private workstation data.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
create table kakaotalk_queue (
id bigint generated by default as identity primary key,
user_id text not null, utterance text not null, callback_url text,
status text default 'pending', response text, created_at timestamptz default now()
The default system prompt is written in Korean and instructs the assistant how to answer, which imposes a language/locale behavior by default. There is no indication in this file that users can opt into another language or that the Korean-only behavior is a documented regional requirement.
In relay mode, the code transmits user_id, utterance, and callback_url to Supabase without any visible user-facing notice or consent mechanism in this file. Because this is a chat integration and the skill description mentions optional local computer connectivity, the privacy risk is elevated: users may not reasonably expect their messages and callback metadata to be queued in an external backend for later processing.
No suspicious patterns detected.