T09 · Insecure Skill Coding Practices
- Location
youdao_api.py:11- Finding
Full Browser Cookie Is Collected and Forwarded Beyond the Required Cookie Set
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill appears purpose-built for Youdao notes, but it asks for a full browser login cookie and can bulk-read or create notes without built-in confirmation controls.
Review before installing. Treat YOUDAO_COOKIE like a password for the Youdao account, avoid storing it longer than needed, rotate it after use, and only run bulk reads or note creation when you have clearly chosen the target folder or note. Use an isolated environment and consider pinning dependencies before installation.
youdao_api.py:11Full Browser Cookie Is Collected and Forwarded Beyond the Required Cookie Set
youdao_api.py:78Authentication Token Is Repeatedly Embedded in Request URLs
requirements.txt:1Third-Party Dependencies Are Installed Without Version or Integrity Pinning
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
url = f"{BASE_URL}/yws/api/personal/file?method=getByPath&keyfrom=web&cstk={cstk}"
headers = build_headers()
data = {"file": "/", "method": "getByPath", "keyfrom": "web", "cstk": cstk}
response = requests.post(url, headers=headers, data=data)
if response.status_code == 400:
url_get = f"{BASE_URL}/yws/api/personal/file?method=getByPath&keyfrom=web&file=%2F&cstk={cstk}"
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
url = f"{BASE_URL}/yws/api/personal/file?method=getByPath&keyfrom=web&cstk={cstk}"
headers = build_headers()
data = {"file": "/", "method": "getByPath", "keyfrom": "web", "cstk": cstk}
response = requests.post(url, headers=headers, data=data)
if response.status_code == 400:
url_get = f"{BASE_URL}/yws/api/personal/file?method=getByPath&keyfrom=web&file=%2F&cstk={cstk}"
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
if response.status_code == 400:
url_get = f"{BASE_URL}/yws/api/personal/file?method=getByPath&keyfrom=web&file=%2F&cstk={cstk}"
response = requests.get(url_get, headers=headers)
if response.status_code != 200:
print("Warning: Failed to get root directory via API. You may need to specify --dir explicitly.", file=sys.stderr)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
cstk = get_cstk()
url = f"{BASE_URL}/yws/api/personal/file/{dir_id}?all=true&f=true&len=100&sort=1&isReverse=false&method=listPageByParentId&keyfrom=web&cstk={cstk}"
headers = build_headers()
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"Error in list_files: {response.text}", file=sys.stderr)
response.raise_for_status()
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
if root_dir:
params["parentId"] = root_dir
response = requests.post(url, headers=headers, params=params, data={"cstk": cstk})
if response.status_code != 200:
print(f"Error in search_notes: status={response.status_code} body={response.text}", file=sys.stderr)
response.raise_for_status()
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"convert": "true",
"cstk": cstk
}
response2 = requests.post(url, headers=headers, data=data_no_editor)
if response2.status_code == 200:
content = process_response_content(response2)
if content and "当前客户端版本过低" not in content and "当前笔记内容暂时无法查看" not in content:
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
# Third attempt: try fallback endpoint for new doc formats
url_new_format = f"{BASE_URL}/yws/api/personal/file/{file_id}?method=download&all=true&f=true&len=30&sort=1&isReverse=false&keyfrom=web&cstk={cstk}"
response3 = requests.get(url_new_format, headers=headers)
if response3.status_code == 200:
content = process_response_content(response3)
if content and "当前客户端版本过低" not in content and "当前笔记内容暂时无法查看" not in content:
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
# Fourth attempt: try basic sync download
url_get2 = f"{BASE_URL}/yws/api/personal/sync?method=download&keyfrom=web&id={file_id}&cstk={cstk}"
response4 = requests.get(url_get2, headers=headers)
if response4.status_code == 200:
content = process_response_content(response4)
if content:
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"transactionTime": now,
"transactionId": file_id
}
push_resp = requests.post(push_url, headers=headers, params=push_params, data=push_payload)
last_push_resp = push_resp
if push_resp.status_code == 200:
push_result = parse_push_result(push_resp, note_name)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
]
last_response = last_push_resp
for payload in payload_candidates:
response = requests.post(url, headers=headers, params=params, data=payload)
last_response = response
if response.status_code == 200 and note_exists_in_parent(note_name):
result = response.json()
The skill claims to operate 'safely' and highlights read/search/create, but the content also enables undeclared directory listing and bulk reading of all notes in a folder, while relying on a full browser cookie that may grant broad account access. This mismatch can mislead users and orchestration systems about the real data access breadth, increasing the risk of unexpected mass disclosure of private notes.
The README instructs users to extract and store a full authenticated browser cookie, including session-bearing values, in an environment variable so the skill can access their account. This exposes reusable credentials that may be leaked through process inspection, shell history, logs, crash reports, or other tooling, and it normalizes unsafe credential handling without warning users about account takeover risk.
The skill declares no explicit tool scope or permission boundary even though it requires environment-variable secrets and network access. Without declared limits, an agent may invoke a capability that can access authenticated note data using a high-privilege browser cookie, reducing transparency and making overreach harder to detect or control.
The activation text says to call the skill whenever the user asks to operate on Youdao notes, which is broad and may trigger the skill for loosely related prompts without sufficient narrowing or confirmation. In a skill that can access authenticated personal notes, broad invocation conditions increase the chance of unnecessary data access or unintended actions.
The markdown explicitly instructs the agent to read all notes in a directory in one operation, but it does not warn about the privacy implications or require explicit user acknowledgment for mass content access. Bulk extraction materially raises the blast radius versus single-note reads, especially for personal or work logs that may contain sensitive information.
The skill depends on a full browser session cookie stored in YOUDAO_COOKIE, which is highly sensitive and effectively grants account access. Without strong disclosure and safer secret-handling patterns, users or operators may expose long-lived session credentials to logs, process listings, or broader runtime access.
The code automatically transmits the full authenticated cookie header on every request, but the skill metadata does not disclose this sensitive authentication mechanism. In agent environments, hidden credential use is risky because it can surprise users and widen the blast radius if the runtime or logs are compromised.
The skill performs remote write operations that create notes in the user's account, but there is no built-in confirmation or user-warning mechanism in the implementation. In an autonomous agent context, silent writes are more dangerous because they can alter user data unexpectedly or be abused for spam, persistence, or misleading records.
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
parent_dir = get_root_dir()
headers = build_headers()
headers["Content-Type"] = "application/x-www-form-urlencoded"
time_mod = __import__("time")
now = int(time_mod.time())
file_id = "WEB" + os.urandom(16).hex().upper()
note_name = title.strip()
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
]
last_response = last_push_resp
for payload in payload_candidates:
response = requests.post(url, headers=headers, params=params, data=payload)
last_response = response
if response.status_code == 200 and note_exists_in_parent(note_name):
result = response.json()
The skill implements read_all_in_dir, which performs bulk extraction of every note in a directory, exceeding the metadata description of read/search/create at the single-note level. In an agent setting, this materially increases data-exposure risk because a broad request can retrieve far more user content than necessary for the task.
The README content and usage examples are entirely in Chinese and instruct the user to interact with the agent in Chinese-style prompts, but do not indicate that language is optional or limited for a justified regional reason. Under the language/locale policy, skills should not implicitly force a specific language without opt-in or documentation.
The dependency 'requests' is specified without a version pin, which makes builds non-reproducible and can cause the skill to install a vulnerable or incompatible release depending on when and where it is deployed. In a note-taking integration that likely handles authentication and user data, pulling an unbounded version of an HTTP client library increases supply-chain and patch-management risk.
requests
brotli
The manifest includes 'requests' without a pinned version, and the package has multiple known advisories across releases. Because the version is unspecified, there is no way to verify from this file alone whether deployment will use a patched version, leaving open the possibility of shipping a vulnerable HTTP stack in a skill that may transmit credentials or access note content.
The dependency 'brotli' is also unpinned, so the installed package version is not deterministic and may vary across environments. That creates avoidable supply-chain risk and makes it difficult to verify whether known security fixes are present.
requests
brotli
No suspicious patterns detected.