T09 · Insecure Skill Coding Practices
- Location
scripts/resize_cover.py:35- Finding
Unrestricted Remote Image Retrieval Enables Server-Side Request Forgery
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill matches its banner-promotion purpose, but it can change business promotions and upload assets while using unsafe runtime package installation and unrestricted image downloading.
Install only if you trust the MWS environment and are comfortable with this skill making real banner promotion changes, adding promotion admins, and uploading images. Before use, remove runtime pip installation, pin dependencies, restrict remote image downloads to approved domains or local files, and handle NOS tokens in a private temporary directory with immediate cleanup.
scripts/resize_cover.py:35Unrestricted Remote Image Retrieval Enables Server-Side Request Forgery
scripts/resize_cover.py:12Automatic Installation of Unpinned Python Dependencies at Runtime
references/image-upload-nos.md:96NOS Upload Token Can Remain in a Predictable Plaintext Working-Directory File
The declared description presents a broad operational skill for 灵渠 Banner promotion lifecycle tasks, including creation, querying, copying, preemption/cancel-preemption, status updates, modification, creative handling, and material association. The supplied code does not perform those actions. It only parses promotion-detail JSON, lists available delivery resource options, infers a likely resource selection, normalizes selected fields, and prints suggested external commands for validating a standard resource and extracting templates for positions such as discovery-page banner endpoints. This is related to banner creative/material preparation, so it is in-domain, but it is only a narrow helper utility rather than the described end-to-end banner promotion skill. Therefore the description materially overstates the implemented capabilities.
The declared description is about business operations for banner promotion management in Lingqu, including create/query/copy/modify/status/material workflows. The supplied code does none of that. Instead, it is an image-processing utility that fetches an image from a URL or local path, resizes and crops it to target dimensions, and writes an output JPEG. This is a materially different primary purpose and introduces undeclared capabilities such as network image retrieval, filesystem image processing, and package installation. Therefore the description does not accurately represent the code.
声明描述的是一个面向 Banner 推广配置与管理的 skill,涵盖创建、查询、复制、修改、预占/取消预占、状态修改、创意和物料关联等完整业务能力。但给定代码仅是一个离线分析脚本:读取资源列表 JSON,整理默认跳转链接、角标、作者等资源元数据,并在缺失时输出提示。它没有任何 Banner 推广对象的创建、查询、复制、修改、状态切换或素材关联逻辑,也没有体现声明中的触发场景。虽然提取“默认角标/跳转链接”可能可作为 Banner 配置的辅助步骤,但这只是支持性子功能,无法代表声明的主要用途,因此描述与实际行为存在明显不匹配。
The declared description describes an operational advertising skill for managing Lingqu banner promotions and related creative/material workflows. The supplied code does none of those actions. It is a local analysis/reporting utility that parses JSON input and summarizes template schema fields for display. There are no API calls, no banner promotion CRUD/state operations, no reservation handling, and no material association logic. This is a materially different primary purpose and capability set, so the description does not accurately represent the code.
There is a clear description-behavior mismatch. The declared skill is about managing banner promotions and related creative/material operations in a business workflow. The actual code only validates image dimensions, optionally downloading an image from a URL, and does not implement any banner configuration, CRUD operations, status changes, reservation logic, or material-linking behavior. While image validation could theoretically support banner asset checks, this chunk’s primary purpose is materially different and far narrower than the declared skill.
Referenced artifact was not completely inspected
reative` 插入失败、`name ... null`,这是 `creative.name` 缺失,不是数据库问题。立即停止原 payload 重试,按 [references/creative-dynamic-material.md](references/creative-dynamic-material.md
Referenced artifact was not completely inspected
reative` 插入失败、`name ... null`,这是 `creative.name` 缺失,不是数据库问题。立即停止原 payload 重试,按 [references/creative-dynamic-material.md](references/creative-dynamic-material.md
Referenced artifact was not completely inspected
reative` 插入失败、`name ... null`,这是 `creative.name` 缺失,不是数据库问题。立即停止原 payload 重试,按 [references/creative-dynamic-material.md](references/creative-dynamic-material.md
Referenced artifact was not completely inspected
reative` 插入失败、`name ... null`,这是 `creative.name` 缺失,不是数据库问题。立即停止原 payload 重试,按 [references/creative-dynamic-material.md](references/creative-dynamic-material.md
Referenced artifact was not completely inspected
reative` 插入失败、`name ... null`,这是 `creative.name` 缺失,不是数据库问题。立即停止原 payload 重试,按 [references/creative-dynamic-material.md](references/creative-dynamic-material.md
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
}'
`plan-pack-resource-list` 的资源 ID 参数必须叫 `resourceIds`(复数,字符串,多个 ID 用英文逗号拼接),禁止写 `resourceId`。如果看到错误 `Required String parameter 'resourceIds' is not present`,不要继续换 HTTP/curl 或猜其它字段,直接把参数从 `resourceId` 改为 `resourceIds` 并通过 `mws link plan-pack-resource-list` 重试:
```bash
mws link plan-pack-resource-list --env ${MWS_ENV} --params '{"resourceType":"album","resourceIds":"382794761"}' --format json
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
}'
`plan-pack-resource-list` 的资源 ID 参数必须叫 `resourceIds`(复数,字符串,多个 ID 用英文逗号拼接),禁止写 `resourceId`。如果看到错误 `Required String parameter 'resourceIds' is not present`,不要继续换 HTTP/curl 或猜其它字段,直接把参数从 `resourceId` 改为 `resourceIds` 并通过 `mws link plan-pack-resource-list` 重试:
```bash
mws link plan-pack-resource-list --env ${MWS_ENV} --params '{"resourceType":"album","resourceIds":"382794761"}' --format json
The script invokes a shell to install packages at runtime, which creates an unnecessary command-execution and supply-chain risk during normal image processing. Even though the command string is mostly static, it still executes external code from package repositories and depends on the runtime environment, making behavior non-deterministic and unsafe for a banner-processing helper.
from PIL import Image
except ImportError:
print("Installing Pillow...", file=sys.stderr)
os.system(f"{sys.executable} -m pip install Pillow requests -q")
from PIL import Image
try:
This second shell-based package installation repeats the same unsafe pattern: the script automatically fetches and installs code at execution time instead of relying on pre-provisioned dependencies. That exposes the environment to unintended package execution, repository tampering, and operational surprises unrelated to the stated image-resize function.
import requests
except ImportError:
print("Installing requests...", file=sys.stderr)
os.system(f"{sys.executable} -m pip install requests -q")
import requests
The script invokes a shell command to install Pillow at runtime using os.system. Even though the command string is not directly user-controlled, executing package installation as a side effect of validation is dangerous because it enables unreviewed code download/execution from package indexes and expands the tool's privileges unexpectedly.
from PIL import Image
except ImportError:
print("Installing Pillow...", file=sys.stderr)
os.system(f"{sys.executable} -m pip install Pillow -q")
from PIL import Image
This os.system call installs requests dynamically at runtime, again causing the validator to execute external package-management commands as a side effect. In combination with the URL-loading feature, this broadens the attack surface and can result in execution of untrusted code or supply-chain compromise if the environment or package source is tampered with.
import requests
except ImportError:
print("Installing requests...", file=sys.stderr)
os.system(f"{sys.executable} -m pip install requests -q")
import requests
response = requests.get(path_or_url, timeout=30)
response.raise_for_status()
The skill documents extensive shell, file, and network-capable operations but does not declare any explicit tool scope such as allowed-tools or permissions. In an agent runtime, that omission weakens least-privilege boundaries and can allow the skill to be invoked with broader capabilities than users or reviewers expect.
The trigger phrases include broad generic terms such as 查询/创建/修改/素材 that can overlap with normal conversation and unrelated requests. Overbroad activation increases the chance that the skill is invoked unexpectedly for sensitive write actions, especially because the skill can create, modify, upload, and change status in external systems.
The skill instructs retrieval of a backend NOS token and direct object-storage upload without a clear user-facing warning, approval gate, or token-handling constraints. In a skill that processes user-supplied URLs and local files, this raises the risk of credential misuse, unauthorized uploads, or accidental exposure of sensitive upload capability.
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.
| `name` | 最终提交推广名称,格式为 `【内容大类/版权类型/战略类型/投放资源标签】推广基础名称 –【二级部门】`,长度不超过 50 |
| `projectName` | 项目名称,使用用户真实项目名 |
| `projectOwner` | 项目负责人邮箱前缀,不传完整邮箱;优先取当前会话真实发起人或当前登录用户。只有 `grp.`/机器人执行且无法确认真实发起人时才问用户 |
| `creatorDept` | AI VO 不直接传该字段;服务端从推广名称末尾 `–【二级部门】` 解析并写入。本部门龙虾/运营审核机器人场景默认 `内容运营部`;非 `grp.` 执行身份可从 `pms-login-status.data.depList[level=2].name` 取。其它 `grp.` 场景无法确认本部门时才问真实二级部门 |
| 投放端 | 用户选择的端,内部转换为 `relatedPositionCodes` |
| `startTime` / `endTime` | `yyyy-MM-dd HH:mm:ss`;只给日期时按自然日闭区间,开始日 00:00:00,结束日 23:59:59;同一天小时段不拆,跨自然日才可能按天拆分,最多 7 天;拆分前必须让用户在“拆分/不拆分”中二选一 |
| `contentCategory` | 内容大类 code |
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.
| `name` | 最终提交推广名称,格式为 `【内容大类/版权类型/战略类型/投放资源标签】推广基础名称 –【二级部门】`,长度不超过 50 |
| `projectName` | 项目名称,使用用户真实项目名 |
| `projectOwner` | 项目负责人邮箱前缀,不传完整邮箱;优先取当前会话真实发起人或当前登录用户。只有 `grp.`/机器人执行且无法确认真实发起人时才问用户 |
| `creatorDept` | AI VO 不直接传该字段;服务端从推广名称末尾 `–【二级部门】` 解析并写入。本部门龙虾/运营审核机器人场景默认 `内容运营部`;非 `grp.` 执行身份可从 `pms-login-status.data.depList[level=2].name` 取。其它 `grp.` 场景无法确认本部门时才问真实二级部门 |
| 投放端 | 用户选择的端,内部转换为 `relatedPositionCodes` |
| `startTime` / `endTime` | `yyyy-MM-dd HH:mm:ss`;只给日期时按自然日闭区间,开始日 00:00:00,结束日 23:59:59;同一天小时段不拆,跨自然日才可能按天拆分,最多 7 天;拆分前必须让用户在“拆分/不拆分”中二选一 |
| `contentCategory` | 内容大类 code |
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.
| `name` | 最终提交推广名称,格式为 `【内容大类/版权类型/战略类型/投放资源标签】推广基础名称 –【二级部门】`,长度不超过 50 |
| `projectName` | 项目名称,使用用户真实项目名 |
| `projectOwner` | 项目负责人邮箱前缀,不传完整邮箱;优先取当前会话真实发起人或当前登录用户。只有 `grp.`/机器人执行且无法确认真实发起人时才问用户 |
| `creatorDept` | AI VO 不直接传该字段;服务端从推广名称末尾 `–【二级部门】` 解析并写入。本部门龙虾/运营审核机器人场景默认 `内容运营部`;非 `grp.` 执行身份可从 `pms-login-status.data.depList[level=2].name` 取。其它 `grp.` 场景无法确认本部门时才问真实二级部门 |
| 投放端 | 用户选择的端,内部转换为 `relatedPositionCodes` |
| `startTime` / `endTime` | `yyyy-MM-dd HH:mm:ss`;只给日期时按自然日闭区间,开始日 00:00:00,结束日 23:59:59;同一天小时段不拆,跨自然日才可能按天拆分,最多 7 天;拆分前必须让用户在“拆分/不拆分”中二选一 |
| `contentCategory` | 内容大类 code |
This markdown file is written as operational instructions entirely in Chinese, beginning with the section header at L003, and nowhere offers users or operators a language/locale choice. Under the policy rule, forcing a specific language without opt-in is a natural-language policy violation unless the locale restriction is explicitly justified, which is not present here.
The reference states '本 skill 不修改已有创意' at L016, but immediately instructs saving via promotion-update-creative, which is an update operation on promotion materials. Later guidance also allows retaining creative.id for existing creatives and modifying entries in the existing promotionAliasCreativeList, which contradicts the claim that existing creatives are not modified.
L552 says the skill '仅支持按当前模板重新保存一份推广物料' and does not modify existing creatives, yet L560 explicitly says that when a position alias already has multiple creatives, the operator should read the original promotionAliasCreativeList, add/delete/modify corresponding items, and submit the whole list. That is an active contradiction between stated intent and required behavior.
No suspicious patterns detected.