T09 · Insecure Skill Coding Practices
- Location
scripts/call_api.py:54- Finding
API Credential Disclosure Through a Configurable Upstream Endpoint
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill is presented as a chess FEN visualizer, but it requires and persists an unrelated API key and sends requests to a configurable remote MCP service.
Review before installing. This skill asks users for an XBY API key, stores it in the project .env file, and sends FEN input plus that key to a remote service. Install only if you trust the XiaoBenYang service and the publisher, are comfortable with local plaintext credential storage, and can control the working directory and environment variables used to run it.
scripts/call_api.py:54API Credential Disclosure Through a Configurable Upstream Endpoint
scripts/config.py:43Plaintext API Key Persistence Without Explicit File-Security Controls
Mixing characters from multiple Unicode scripts in a single identifier is a common technique to create visually ambiguous tool names.
The skill claims to visualize chess positions but documents reading/writing .env, persisting XBY_APIKEY, and configuring unrelated remote service parameters. This creates unnecessary credential handling and persistent state for a task that should be self-contained, increasing the risk of secret collection, accidental leakage, and misuse of the host environment.
The skill claims to visualize chess positions but documents reading/writing .env, persisting XBY_APIKEY, and configuring unrelated remote service parameters. This creates unnecessary credential handling and persistent state for a task that should be self-contained, increasing the risk of secret collection, accidental leakage, and misuse of the host environment.
The SKILL.md explicitly requires collecting an API key and using an external API even though FEN validation/visualization does not need either. In context this is a strong red flag for credential harvesting or disguised remote-service invocation, made more dangerous because the instructions tell the agent to ask the user for the secret and persist it.
该文件实现的并不是受限于FEN校验/棋盘可视化的专用逻辑,而是一个可向外部“小笨羊MCP API”发送任意工具名、任意参数和可选mcp_id的通用代理调用器。对于一个宣称仅提供国际象棋FEN验证可视化的技能,这种通用外呼能力明显超出最小权限范围,可被上层代码复用为任意外部工具调用与数据转发通道,扩大数据外泄、越权功能调用和隐蔽能力漂移的风险。
The file is materially inconsistent with the declared chess FEN validation/visualization purpose and instead implements configuration for an unrelated Gaokao service plus API-key handling. In an agent skill, this kind of scope mismatch is dangerous because it can mask hidden data flows, unexpected remote dependencies, or credential collection behavior that users would not reasonably expect from a local chess utility.
The docstring explicitly identifies the code as configuration for a different Gaokao skill, directly contradicting the manifest's chess-service description. In a security review, this mismatch is a strong indicator of repurposed or deceptive code, making any credential or network behavior significantly more suspicious.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
model_config = SettingsConfigDict(
env_prefix="XBY_GAOKAO_",
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
The code forcibly reads the .env file and extracts a specific API key outside the normal settings abstraction, adding bespoke secret-access logic unrelated to the stated chess purpose. In context, this increases suspicion because the skill is expected to validate FEN locally, not inspect and harvest credentials from local configuration files.
default_year: int = 2025
def model_post_init(self, __context):
# 强制从 .env 文件读取 XBY_APIKEY
env_path = Path(".env")
if env_path.exists():
content = env_path.read_text(encoding="utf-8")
Opening and reading .env directly is a form of local secret access, and in this skill context it is unnecessary for a chess FEN utility. Because the file's functionality is mismatched with the manifest, this direct file access is more dangerous than it would be in a legitimately networked service.
def model_post_init(self, __context):
# 强制从 .env 文件读取 XBY_APIKEY
env_path = Path(".env")
if env_path.exists():
content = env_path.read_text(encoding="utf-8")
for line in content.splitlines():
The environment variable override continues the secret-access pattern by pulling XBY_APIKEY into runtime state for an unrelated skill. On its own this is standard practice, but within a purported local chess tool that already shows deceptive scope mismatch, this contributes to unjustified credential handling and raises the risk of misuse.
if line.startswith("XBY_APIKEY="):
self.api_key = line.split("=", 1)[1].strip()
break
# 如果环境变量有值,覆盖 .env 的值
env_val = os.getenv("XBY_APIKEY", "")
if env_val:
self.api_key = env_val
Persisting API credentials to .env and copying them into the process environment creates long-lived secret exposure unrelated to the chess use case. If the working directory, logs, backups, or repository contents are exposed, the stored key can be recovered and abused to access the associated external service.
This function is expressly designed to persist an API key into .env, turning transient secret input into durable local storage. In the context of a chess skill with no clear need for credentials, this behavior creates unnecessary secret exposure and is consistent with collecting or retaining credentials beyond user expectations.
def save_api_key_to_env(api_key: str) -> bool:
"""将API key保存到.env文件"""
try:
env_path = Path(".env")
lines = []
if env_path.exists():
lines = env_path.read_text(encoding="utf-8").splitlines()
The skill declares no explicit tool scope or permissions while its documented/project capabilities include environment access, file read/write, and outbound network usage. In an agent setting this broad, undeclared capability boundary increases the chance of unintended secret access, persistent modification, or exfiltration because reviewers and users cannot accurately understand what the skill may do.
The entire skill description and operating instructions are written only in Chinese, and the workflow provides no opt-in or alternative language behavior for users. Under the language/locale policy, a skill should not force a specific language unless it offers a choice or clearly documents a justified regional constraint.
The workflow references unrelated tooling such as search_schools, indicating the skill may be repurposed from another domain without proper review. This kind of cross-domain copy/paste error undermines trust in the skill's behavior and can cause the agent to invoke unintended functions or send user data to the wrong backend.
The project structure and return-value handling describe a generic API-backed skill (call_api.py, config management, raw remote JSON) instead of a local chess renderer. This discrepancy suggests the skill may expose more capabilities than advertised and encourages direct presentation of remote raw data, which can leak sensitive content or propagate untrusted output into the agent conversation.
代码会把调用参数通过HTTP发送到外部服务,并同时在请求头中附带API密钥,但从该实现看不到任何用户侧提示、确认或最小化发送机制。若上层把用户输入、上下文或敏感内容放入params,此技能会在不透明的情况下将数据出站到第三方服务,造成隐私泄露和凭证使用边界不清的问题。
The presence of base_url, mcp_id, and api_key fields introduces remote-service and credential dependencies that are not justified by the advertised local chess functionality. This expands the attack surface by enabling outbound connections and secret handling where users would expect simple local processing only.
The function silently writes the supplied API key into a local .env file without any user-facing confirmation, warning, or disclosure. This can cause users or operators to unknowingly leave credentials on disk where they may later be committed, exfiltrated, or read by other local processes.
The dependency declaration uses a lower-bound constraint only (requests>=2.31.0), which permits installation of many different future versions and prevents reproducible builds. This increases supply-chain risk because a later vulnerable or incompatible release could be resolved without review, and the separate advisory signal for requests makes that uncertainty more relevant.
requests>=2.31.0
pydantic>=2.7.0
pydantic-settings>=2.2.0
python-dotenv>=1.0.1
The manifest does not pin requests, and the package has multiple known advisories across versions, so it is impossible to verify from this file whether the deployed version is affected. In a network-capable MCP service, unresolved requests version ambiguity raises risk because HTTP client flaws can affect confidentiality or request handling if this library is used.
pydantic>=2.7.0 is unpinned, so deployments may resolve to different versions over time, including versions with known defects or security issues. In an MCP service that validates user-provided input, dependency behavior matters directly, so non-reproducible resolution is a genuine supply-chain weakness.
requests>=2.31.0
pydantic>=2.7.0
pydantic-settings>=2.2.0
python-dotenv>=1.0.1
pydantic has known advisories in some releases, but the unpinned requirement prevents determining whether the installed version is safe. Given this service's stated role in validating FEN input, a vulnerable validation library could be more relevant than in a passive utility because attacker-controlled input is expected.
pydantic-settings>=2.2.0 allows arbitrary newer versions to be installed, making the build non-deterministic and complicating assurance that a safe version is used. Because settings libraries often load environment and secrets-related data, version uncertainty can expose the service to configuration-handling flaws.
requests>=2.31.0
pydantic>=2.7.0
pydantic-settings>=2.2.0
python-dotenv>=1.0.1