Back to skill

Security audit

Kakaotalk

Security checks for vulnerabilities and agentic risk

Overview

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.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/watcher.py:89
Finding
Unauthenticated Relay Can Exfiltrate Private Agent Memory to an Attacker-Controlled Callback<![CDATA[ ## Vulnerability Details **File Location**: `api/index.py:92-104`; `scripts/watcher.py:64-82, 85-128, 145-148` **Vulnerability Type**: Missing authentication, excessive local-file access, prompt injection, and arbitrary outbound callback **Risk Level**: Critical ### Vulnerable Code ```python # api/index.py:92-104 user_request = body.get("userRequest", {}) utterance = user_request.get("utterance", "").strip() user_id = user_request.get("user", {}).get("id", "unknown") callback_url = user_request.get("callbackUrl", "") # 모드 결정 is_relay_mode = bool(SUPABASE_URL and SUPABASE_KEY) if is_relay_mode: # [Relay Mode] 로컬 처리를 위해 DB 저장 if not callback_url: self._send_json(_kakao_response("AI 챗봇(콜백) 설정을 켜주세요.")) return if _save_to_queue(user_id, utterance, callback_url): ``` ```python # scripts/watcher.py:64-82 def send_callback(url, text): """카카오톡 서버로 답장 발송""" payload = json.dumps({ "version": "2.0", "template": { "outputs": [{"simpleText": {"text": text}}] } }).encode("utf-8") req = urllib.request.Request(url, data=payload, method="POST", headers={ "Content-Type": "application/json" }) try: with urllib.request.urlopen(req) as resp: print(f"✅ 발송 성공: {text[:20]}...") return True except Exception as e: print(f"❌ 발송 실패: {e}") return False ``` ```python # scripts/watcher.py:89-110 # 1. 로컬 메모리 읽기 (Context Injection) memory_path = os.path.expanduser("~/.openclaw/workspace/MEMORY.md") memory_context = "" if os.path.exists(memory_path): with open(memory_path) as f: memory_context = f.read()[:2000] # 너무 길면 자름 # 2. 시스템 프롬프트 구성 system_prompt = f"""너는 라온이다. (Mac Studio에서 실행 중) 사용자의 질문: {utterance} [장기 기억 (MEMORY.md)] {memory_context} 사용자에게 친절하게, 그리고 기억을 바탕으로 대답해.""" # 3. LLM 호출 (Gemini) return _call_gemini_direct(system_prompt, utterance) ``` ```python # scripts/watcher.py:116-128 url = f"https://gener ...[truncated 2638 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require cryptographic authentication of every inbound Kakao webhook before queue insertion and reject requests when authentication is unavailable. 2. Do not accept arbitrary callback URLs. Permit only documented Kakao HTTPS callback hosts and bind each callback to authenticated request metadata. 3. Resolve callback hostnames and reject loopback, private, link-local, multicast, reserved, and cloud metadata addresses. 4. Do not automatically load `MEMORY.md` for externally initiated requests. Require explicit, per-user authorization and expose only narrowly selected memory records. 5. Treat memory as confidential data rather than instructions. Apply prompt-injection controls and prevent the model from reproducing raw memory. 6. Add output data-loss-prevention checks before callbacks are sent. 7. Use a least-privilege Supabase key with row-level security instead of a service-role key where possible. 8. Add queue provenance fields, replay protection, rate limits, record expiration, and atomic message claiming. 9. Clearly disclose to users when message and memory content will be sent to Gemini. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/server.py:279
Finding
Optional Authentication and Unrestricted Callback URL Enable Blind SSRF<![CDATA[ ## Vulnerability Details **File Location**: `scripts/server.py:122-130, 279-294, 339-365, 431-450` **Vulnerability Type**: Missing fail-closed authentication and server-side request forgery **Risk Level**: High ### Vulnerable Code ```python # scripts/server.py:122-130 def _verify_signature(body: bytes, signature: str) -> bool: """KAKAO_CALLBACK_SECRET 기반 HMAC-SHA1 검증. 시크릿 없으면 스킵.""" if not KAKAO_SECRET: return True expected = hmac.new( KAKAO_SECRET.encode("utf-8"), body, hashlib.sha1, ).hexdigest() return hmac.compare_digest(expected, signature or "") ``` ```python # scripts/server.py:279-294 def _send_callback(callback_url: str, text: str) -> None: """LLM 완료 후 카카오 콜백 URL로 실제 응답 전송.""" try: payload = json.dumps(_kakao_response(text, include_quick_replies=False), ensure_ascii=False).encode("utf-8") req = urllib.request.Request( callback_url, data=payload, 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}") ``` ```python # scripts/server.py:353-365 # utterance / user_id / callbackUrl 추출 user_request = body.get("userRequest", {}) utterance = user_request.get("utterance", "").strip() user_id = user_request.get("user", {}).get("id", "unknown") callback_url = user_request.get("callbackUrl", "") # AI 챗봇 모드에서만 존재 _log(f"📩 user={user_id[:12]}... | utterance={utterance[:60]} | callback={'✅' if callback_url else '❌'}") if not utterance: self._send_json(_kakao_response("메시지를 입력해주세요 😊")) return ``` ```python # scripts/server.py:443-450 if _callback_url and not FORCE_SYNC: if response: _send_callback(_callback_url, response) else: _send_callba ...[truncated 2044 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make webhook authentication mandatory and refuse to start in public mode without a configured secret. 2. Confirm the exact signature algorithm and header format against current official Kakao documentation. 3. Restrict callbacks to an explicit allowlist of documented Kakao HTTPS hostnames. 4. Reject credentials in URLs, non-HTTPS schemes, unusual ports, IP-literal hosts, and malformed URLs. 5. Resolve hostnames before connecting and reject loopback, private, link-local, multicast, reserved, and metadata address ranges. 6. Disable redirects or validate the destination again after every redirect. 7. Apply DNS rebinding protections by connecting only to the validated resolved address while preserving safe TLS hostname verification. 8. Add request body-size limits, per-client rate limiting, concurrency limits, and LLM usage quotas. 9. Avoid logging full callback locations, particularly if query parameters may contain tokens. ]]>

T06 · System Persistence

Error
Location
scripts/install-service.sh:47
Finding
Installer Registers a Cross-Session Auto-Starting launchd Service<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install-service.sh:47-108` **Vulnerability Type**: Persistent user service registration **Risk Level**: High ### Vulnerable Code ```bash # scripts/install-service.sh:47-108 cat > "$PLIST_PATH" << PLIST <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${LABEL}</string> <key>ProgramArguments</key> <array> <string>${PYTHON3}</string> <string>-u</string> <string>${SCRIPT_PATH}</string> </array> <key>EnvironmentVariables</key> <dict> <key>KAKAOTALK_PORT</key> <string>8401</string> <key>OLLAMA_HOST</key> <string>http://localhost:11434</string> <key>GEMINI_API_KEY</key> <string>${GEMINI_API_KEY_VAL}</string> <key>KAKAO_CALLBACK_SECRET</key> <string>${KAKAO_SECRET_VAL}</string> <key>PATH</key> <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string> </dict> <key>WorkingDirectory</key> <string>${SKILL_DIR}</string> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>StandardOutPath</key> <string>${LOG_FILE}</string> <key>StandardErrorPath</key> <string>${ERR_FILE}</string> <key>RunAtLoad</key> <true/> <key>ThrottleInterval</key> <integer>10</integer> </dict> </plist> PLIST chmod 644 "$PLIST_PATH" echo "✅ plist 생성: $PLIST_PATH" launchctl load -w "$PLIST_PATH" ``` ### Technical Analysis The installer writes a LaunchAgent into `~/Library/LaunchAgents`, configures it with `RunAtLoad`, and enables automatic restart following unsuccessful termination through `KeepAlive`. It then loads the service with `launchctl load -w`. The persistence is related to operating an always-on local KakaoTalk webhook and is visibly implemented rather than concealed. However, it exceeds the privileges required by Basic/Vercel mode and causes the ...[truncated 1130 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Keep foreground execution as the default and make persistence an explicit optional installation mode. 2. Display a confirmation explaining the plist path, login-time execution, restart behavior, listening port, and user privileges. 3. Provide a dedicated uninstall script that boots out the service, removes the plist, and optionally deletes logs. 4. Prefer modern `launchctl bootstrap` and `bootout` usage with explicit user domains. 5. Validate that the target script and parent directories are owned by the current user and not writable by other users. 6. Pin execution to a protected installed copy rather than a mutable source checkout. 7. Do not expose the service externally until mandatory authentication is configured. 8. Document how users can inspect, stop, disable, and remove the persistent service. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/install-service.sh:13
Finding
Gemini API Key and Callback Secret Are Duplicated into a World-Readable Plist<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install-service.sh:13-22, 47-75, 104` **Vulnerability Type**: Plaintext secret storage with overly permissive permissions **Risk Level**: Medium ### Vulnerable Code ```bash # scripts/install-service.sh:13-22 ENV_FILE="$HOME/.openclaw/.env" if [[ -f "$ENV_FILE" ]]; then # GEMINI_API_KEY, KAKAO_CALLBACK_SECRET 추출 GEMINI_API_KEY_VAL=$(grep -E '^GEMINI_API_KEY=' "$ENV_FILE" | cut -d= -f2- | tr -d '"' || true) KAKAO_SECRET_VAL=$(grep -E '^KAKAO_CALLBACK_SECRET=' "$ENV_FILE" | cut -d= -f2- | tr -d '"' || true) else GEMINI_API_KEY_VAL="" KAKAO_SECRET_VAL="" fi ``` ```bash # scripts/install-service.sh:65-75 <key>EnvironmentVariables</key> <dict> <key>KAKAOTALK_PORT</key> <string>8401</string> <key>OLLAMA_HOST</key> <string>http://localhost:11434</string> <key>GEMINI_API_KEY</key> <string>${GEMINI_API_KEY_VAL}</string> <key>KAKAO_CALLBACK_SECRET</key> <string>${KAKAO_SECRET_VAL}</string> ``` ```bash # scripts/install-service.sh:104 chmod 644 "$PLIST_PATH" ``` ### Technical Analysis The installer reads two secrets from `~/.openclaw/.env`, embeds them directly into a plaintext LaunchAgent plist, and sets the plist mode to `0644`. This creates a second long-lived secret copy that is readable by other local users whenever parent-directory traversal permissions permit access. The values are interpolated into XML without escaping. Characters with special XML meaning can corrupt the plist or alter its structure. The extraction logic also removes every double quote rather than parsing a defined environment-file format safely. ### Attack Path 1. A user stores a Gemini API key and Kakao callback secret in `~/.openclaw/.env`. 2. The user runs the installer. 3. The script copies both values into `~/Library/LaunchAgents/com.yeomyeonggeori.kakaotalk.plist`. 4. The script changes the plist permissions to `0644`. 5. Another local account, backup process, synchronization tool, or diagnostic ...[truncated 590 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not place secrets directly in the LaunchAgent plist. 2. Load secrets at runtime from a dedicated file owned by the user and restricted to mode `0600`, or use macOS Keychain. 3. Restrict the plist itself to `0600` if it must contain sensitive values. 4. Verify ownership and permissions of `~/.openclaw/.env` before reading it. 5. Use a defined environment parser rather than `grep`, `cut`, and global quote removal. 6. XML-escape every generated plist value and validate the result with `plutil`. 7. Rotate any credentials that have already been written to a permissive plist. 8. Use scoped API keys and enforce billing and usage limits. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/server.py:102
Finding
Webhook Messages and User Identifiers Are Persistently Logged Without Retention or Permission Controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/server.py:102-115, 367`; `scripts/install-service.sh:9-11, 85-89` **Vulnerability Type**: Sensitive information exposure through persistent logging **Risk Level**: Medium ### Vulnerable Code ```python # scripts/server.py:102-115 def _log(msg: str) -> None: """파일 + stdout 동시 출력.""" LOG_DIR.mkdir(parents=True, exist_ok=True) ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") line = f"[{ts}] {msg}" print(line, flush=True) try: with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(line + "\n") except Exception: pass ``` ```python # scripts/server.py:367 _log(f"📩 user={user_id[:12]}... | utterance={utterance[:60]} | callback={'✅' if callback_url else '❌'}") ``` ```bash # scripts/install-service.sh:9-11 LOG_DIR="${KAKAOTALK_LOG_DIR:-$HOME/.openclaw/logs}" LOG_FILE="$LOG_DIR/kakaotalk.log" ERR_FILE="$LOG_DIR/kakaotalk.err.log" ``` ```xml <!-- scripts/install-service.sh:85-89 --> <key>StandardOutPath</key> <string>${LOG_FILE}</string> <key>StandardErrorPath</key> <string>${ERR_FILE}</string> ``` ### Technical Analysis For every ordinary webhook request, the server logs a truncated user identifier and the first 60 characters of the user's message. The same output is written to standard output and an append-only local file. Under launchd, standard output and standard error are also redirected to persistent files. The code does not explicitly create logs with restrictive permissions, rotate them, limit retention, redact sensitive message content, or prevent log injection through control characters embedded in an utterance. Chat messages frequently contain personal, financial, business, or authentication information, making message-body logging sensitive by default. ### Attack Path 1. A user sends confidential information through the KakaoTalk chatbot. 2. The server records the first 60 characters and a stable portion of the user's identifier. ...[truncated 856 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not log message bodies by default. Log only request IDs, coarse outcome codes, and latency. 2. Replace user identifiers with keyed, rotating pseudonymous hashes when correlation is necessary. 3. Create the log directory with mode `0700` and log files with mode `0600`. 4. Add size-based rotation, short retention periods, and secure deletion appropriate to the threat model. 5. Sanitize newline, carriage-return, escape, and other control characters before logging untrusted values. 6. Avoid duplicating application logs through both `_log` and launchd standard-output capture. 7. Document what data is logged and obtain appropriate user consent. 8. Add monitoring and storage limits to prevent disk-exhaustion conditions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
Findings (71)

Tainted flow: 'req' from os.environ.get (line 70, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
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}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 70, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
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:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 275, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
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)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 275, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="POST",
    )

    with urllib.request.urlopen(req, timeout=30) as resp:
        data = json.load(resp)
        return data["candidates"][0]["content"]["parts"][0]["text"].strip()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 275, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
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}")
Confidence
94% confidence
Finding
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.

Tainted flow: 'req' from os.environ.get (line 124, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"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}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 124, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"Content-Type": "application/json"
    })
    try:
        with urllib.request.urlopen(req):
            pass
    except Exception as e:
        print(f"Update Error: {e}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 124, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
"Content-Type": "application/json"
    })
    try:
        with urllib.request.urlopen(req) as resp:
            print(f"✅ 발송 성공: {text[:20]}...")
            return True
    except Exception as e:
Confidence
90% confidence
Finding
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.

Tainted flow: 'req' from os.environ.get (line 124, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
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:
Confidence
91% confidence
Finding
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.

External Script Fetching

High
Category
Supply Chain
Content
---

## 테스트 curl 예시

```bash
# 기본 대화 테스트
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
}' | 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"}},
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
}' | 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"}},
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Credential Access

High
Category
Privilege Escalation
Content
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
Confidence
95% confidence
Finding
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.

External Script Fetching

High
Category
Supply Chain
Content
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:
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Credential Access

High
Category
Privilege Escalation
Content
# ─── 설정 ─────────────────────────────────────────────────────────────────────

# .env 로드 (간이)
def load_env():
    env_path = os.path.expanduser("~/.openclaw/.env")
    if os.path.exists(env_path):
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
# ─── 설정 ─────────────────────────────────────────────────────────────────────

# .env 로드 (간이)
def load_env():
    env_path = os.path.expanduser("~/.openclaw/.env")
    if os.path.exists(env_path):
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
# .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:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

High
Confidence
97% confidence
Finding
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.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
1. [Supabase](https://supabase.com) 프로젝트 생성.
2. **SQL Editor**에서 아래 쿼리 실행:
   ```sql
   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()
Confidence
60% confidence
Finding
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.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
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.

Static analysis

No suspicious patterns detected.