T09 · Insecure Skill Coding Practices
- Location
setup.py:262- Finding
API Credentials Stored in a Plaintext File Without Explicit Permission Restrictions
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill appears to perform the advertised news-brief function, but it stores API keys in a plaintext file and uses an unsafe shell call during setup.
Review this skill before installing if you are not comfortable with Serper and DeepSeek receiving your news queries and article metadata. Use narrowly scoped API keys, avoid running setup from a path controlled by someone else, protect or remove the generated .env file, and prefer environment variables or a secrets manager instead of persistent plaintext keys.
setup.py:262API Credentials Stored in a Plaintext File Without Explicit Permission Restrictions
setup.py:369Shell Command Injection Through an Unquoted Installation Path
The skill description presents a simple news-brief workflow, but the behavior includes outbound calls to third-party APIs and LLM-based summarization that are not fully and explicitly scoped as permissions or operational behaviors. This mismatch is dangerous because users may expose API keys, content, or usage costs to external services without clear informed consent, and the documented sources may not match the actual fetched data path.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def load_dotenv():
"""自动加载 .env 文件中的环境变量"""
env_path = Path(__file__).parent / ".env"
if not env_path.exists():
return
for line in env_path.read_text(encoding="utf-8").splitlines():
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
"""
setup.py
News Brief Skill 一键配置向导
运行后自动生成 .env 和 config.yaml
"""
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
"""
setup.py
News Brief Skill 一键配置向导
运行后自动生成 .env 和 config.yaml
"""
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
"""
setup.py
News Brief Skill 一键配置向导
运行后自动生成 .env 和 config.yaml
"""
import os
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
"""
setup.py
News Brief Skill 一键配置向导
运行后自动生成 .env 和 config.yaml
"""
import os
The setup script writes API keys to a plaintext .env file in the project directory. Local plaintext secret storage may be acceptable for development, but it is still a real security risk because credentials can be exposed through weak file permissions, backups, repository mistakes, or multi-user systems.
def write_env(serper_key: str, deepseek_key: str):
env_path = BASE_DIR / ".env"
env_path.write_text(
f"SERPER_API_KEY={serper_key}\n"
f"DEEPSEEK_API_KEY={deepseek_key}\n",
The setup wizard executes run.py via os.system after collecting and exporting API keys into the process environment. That gives a configuration script authority to run arbitrary additional code from the repository, which is broader than expected for setup and could execute unreviewed or modified logic with access to freshly entered secrets.
os.environ["SERPER_API_KEY"] = serper_key
os.environ["DEEPSEEK_API_KEY"] = deepseek_key
os.chdir(BASE_DIR) # 确保工作目录正确
os.system(f"python {BASE_DIR / 'run.py'} --preview")
if __name__ == "__main__":
The skill advertises capabilities that require environment access, file I/O, network access, and shell execution, but it does not declare any explicit tool scope or permissions. This creates an authorization and transparency gap: users or hosting platforms cannot easily constrain what the skill is allowed to do, increasing the risk of over-privileged execution or abuse if the implementation changes or is compromised.
The description explicitly promises a 'Daily Chinese news brief' and the rest of the document presents the skill as producing Chinese briefs, but it does not offer users any option to choose another language. This is a natural-language locale policy concern because the skill appears to enforce a specific output language by default rather than making it configurable or opt-in.
The module description and prompt strings instruct the model to generate the briefing in Chinese, and the code does not expose any user language selection or opt-in. Under the stated policy, forcing a specific language without user choice can be a locale-policy violation unless clearly justified as region-specific.
The module silently sends collected news titles to a third-party LLM service even though its apparent role is local brief rendering. This creates a data-flow surprise and an external disclosure risk, especially if titles include licensed, embargoed, internal, or user-provided content.
This finding duplicates the same network egress at line 44 but emphasizes the full request payload, including authorization and prompt content. The risk is not the mere use of HTTPS; it is unannounced third-party processing of content within a module that appears to be a renderer.
+ "\n".join(titles[:15])
)
try:
resp = requests.post(
"https://api.deepseek.com/v1/chat/completions",
headers={"Authorization": f"Bearer {deepseek_key}", "Content-Type": "application/json"},
json={"model": "deepseek-chat",
This finding duplicates the same network egress at line 44 but emphasizes the full request payload, including authorization and prompt content. The risk is not the mere use of HTTPS; it is unannounced third-party processing of content within a module that appears to be a renderer.
+ "\n".join(titles[:15])
)
try:
resp = requests.post(
"https://api.deepseek.com/v1/chat/completions",
headers={"Authorization": f"Bearer {deepseek_key}", "Content-Type": "application/json"},
json={"model": "deepseek-chat",
The code transmits news titles to DeepSeek without any visible disclosure, consent, or policy enforcement in this file. Even if titles seem low sensitivity, silent export of content to an external processor can violate user expectations, contractual restrictions, or data-handling requirements.
The hardcoded remote provider endpoint confirms dependence on a third-party service for generated content. In this skill context, that is materially relevant because the skill description suggests news briefing, not off-platform data sharing, making the hidden endpoint more security-significant.
)
try:
resp = requests.post(
"https://api.deepseek.com/v1/chat/completions",
headers={"Authorization": f"Bearer {deepseek_key}", "Content-Type": "application/json"},
json={"model": "deepseek-chat",
"messages": [{"role": "user", "content": prompt}],
A second hidden outbound call is made for 'insight' generation, again transmitting news titles to an external model provider. Multiple undisclosed transmissions increase privacy, compliance, and supply-chain exposure without being necessary for basic formatting functionality.
This duplicate line-72 finding reflects the same outbound request structure for insight generation. The security concern remains silent export of user/content data to an external processor for a non-core feature.
+ "\n".join(titles[:10])
)
try:
resp = requests.post(
"https://api.deepseek.com/v1/chat/completions",
headers={"Authorization": f"Bearer {deepseek_key}", "Content-Type": "application/json"},
json={"model": "deepseek-chat",
This duplicate line-72 finding reflects the same outbound request structure for insight generation. The security concern remains silent export of user/content data to an external processor for a non-core feature.
+ "\n".join(titles[:10])
)
try:
resp = requests.post(
"https://api.deepseek.com/v1/chat/completions",
headers={"Authorization": f"Bearer {deepseek_key}", "Content-Type": "application/json"},
json={"model": "deepseek-chat",
The trend-insight path repeats the same undisclosed data-sharing behavior, exporting content to an external API for nonessential enhancement. Because this is optional enrichment rather than core functionality, the silent disclosure is harder to justify and more likely to be considered a privacy defect.
This endpoint use repeats for the insight-generation path, confirming a second third-party processing flow. The duplicated egress increases the chance of unnoticed data handling and complicates compliance review.
)
try:
resp = requests.post(
"https://api.deepseek.com/v1/chat/completions",
headers={"Authorization": f"Bearer {deepseek_key}", "Content-Type": "application/json"},
json={"model": "deepseek-chat",
"messages": [{"role": "user", "content": prompt}],
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
tbs = "qdr:d" if time_range == "24h" else "qdr:2d"
for attempt in range(3):
try:
resp = requests.post(
"https://google.serper.dev/news",
headers={"X-API-KEY": serper_key, "Content-Type": "application/json"},
json={"q": query, "gl": "us", "hl": "en", "num": num, "tbs": tbs},
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
tbs = "qdr:d" if time_range == "24h" else "qdr:2d"
for attempt in range(3):
try:
resp = requests.post(
"https://google.serper.dev/news",
headers={"X-API-KEY": serper_key, "Content-Type": "application/json"},
json={"q": query, "gl": "us", "hl": "en", "num": num, "tbs": tbs},
This code sends search queries to Serper and later sends collected news titles, sources, dates, and URLs to DeepSeek for verification and enrichment, but the file only contains developer-facing comments and logging. There is no user-facing warning, confirmation, or explanatory notice in this code about external transmission of potentially user-requested data.
The Serper request forces gl to us and hl to en, which imposes a specific region and language regardless of user preference. This is a natural-language/locale policy concern because the skill does not offer a locale choice or document a justified region-specific constraint.
No suspicious patterns detected.