T03 · Remote Payload Retrieval and Execution
- Location
scripts/tools/download_tools.ps1:5- Finding
Mutable Remote Scripts Are Downloaded Without Integrity Verification
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches its PLC automation purpose, but it has high-impact code execution and project-mutation paths that need review before installation.
Install only if you trust the publisher, the internal GitLab source, and every configured workspace. Prefer the bundled reviewed tools over download_tools.ps1 unless you verify the exact remote contents. Use backups and non-production PLC projects first, review env.json paths carefully, avoid untrusted workspaces containing generator_override.py, and consider running with -NoKill to avoid terminating unrelated InoProShop sessions.
scripts/tools/download_tools.ps1:5Mutable Remote Scripts Are Downloaded Without Integrity Verification
scripts/tools/run_script.ps1:91Generate Alias Can Be Silently Hijacked by a Workspace Script
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
---
name: codesys-toolkit
version: 1.0.0
description: "Automate CODESYS/InoProShop PLC programming �� project generation, POU export/patching, compile c..."
Referenced artifact was not completely inspected
| `generator_runner.py` | IronPython | 工程生成:模板→创建 POU/GVL/DUT→挂�?Task→编�?|
The documentation explicitly describes actions that kill existing InoProShop processes and patch or generate PLC projects, but it does not prominently warn about destructive side effects, backup requirements, or the risk of modifying production engineering artifacts. In an industrial automation context, unclear disclosure around process termination and project mutation can lead to accidental disruption, data loss, or unsafe deployment workflows.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
try:
if not os.path.exists(os.path.dirname(_err_early)):
os.makedirs(os.path.dirname(_err_early))
with open(_err_early, 'ab') as _fe:
_fe.write(u'[FATAL] workspace_dir \u672a\u8bbe\u7f6e\uff0c\u8bf7\u5148\u5728 env.json \u4e2d\u5199\u5165 workspace_dir \u5b57\u6bb5\n'.encode('utf-8'))
except:
pass
The file header describes a narrow purpose: exporting POUs from a project into the st/ directory. In addition to that, the implementation removes the entire existing st/ tree before recreating it (L202-L206), writes .committed.json for future patch tracking (L381-L410), and writes task_mounts.json containing task configuration details (L412-L469). These behaviors extend beyond the stated intent in the documentation.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
try:
if not os.path.exists(os.path.dirname(_err_early)):
os.makedirs(os.path.dirname(_err_early))
with open(_err_early, 'ab') as _fe:
_fe.write(u'[FATAL] workspace_dir \u672a\u8bbe\u7f6e\uff0c\u8bf7\u5148\u5728 env.json \u4e2d\u5199\u5165 workspace_dir \u5b57\u6bb5\n'.encode('utf-8'))
except:
pass
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
os.makedirs(_log_dir)
# reopen log in the correct location
_log_path = os.path.join(_log_dir, 'export_pou_log.txt')
open(_log_path, 'wb').close()
log('--- export_pou.py start ---')
log('project : ' + PROJ_PATH)
The script unconditionally deletes the entire st/ directory with shutil.rmtree(_st_dir) before rebuilding it. In this skill context, st/ is a workspace export area, so the main risk is destructive data loss or accidental deletion of unintended files if workspace_dir is misconfigured or manipulated.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
_tm_json = json.dumps(_tm_out, ensure_ascii=False, indent=2)
if isinstance(_tm_json, unicode):
_tm_json = _tm_json.encode('utf-8')
with open(_tm_path, 'wb') as _tmf:
_tmf.write(_tm_json)
log(u'task_mounts.json written ({} tasks)'.format(len(_tm_out)))
except Exception as _tme:
The comments near the configuration loading state that task_mounts are read from workspace_dir/task_mounts.json, implying they will be used to control task mounting. Later, the script deletes task_mounts.json and the active mounting logic explicitly uses a fallback strategy that appends program calls to a template PROGRAM, while create=true entries only create empty tasks and do not mount POUs as described.
The script reads the template path from env.json and copies whatever path is supplied into the workspace without constraining it to an approved base directory or validating that it is an expected project template. In this automation context, that enables path traversal or unintended file access if an attacker can influence env.json, causing the tool to ingest arbitrary local files as project inputs and potentially poison downstream project generation.
# ── 复制模板(先删旧文件避免只读属性冲突) ──
if os.path.exists(_new_project):
os.remove(_new_project)
shutil.copy(_template, _new_project)
log(u'Copied template -> ' + _new_project)
# ── 打开工程,置询等待加载(最长 30s) ──
The comments describe two strategies, with Strategy A using the official task pous API when task_mounts is configured. However, the actual execution path below always uses Strategy B to append calls into an existing template PROGRAM, and task_mounts handling is reduced to optionally creating empty tasks, which materially contradicts the documented intent.
The build routine explicitly parses compiler categories and summaries using Chinese terms such as '编译', '编译完成', '错误', and '警告', while the file comments and user-facing context are also written in Chinese. This creates a locale dependency that assumes a Chinese environment and does not offer any user choice or documented opt-in, matching the language/locale policy violation criteria.
The file header describes the tool as a POU patcher that opens a project, overwrites specified POUs, saves, and compiles. In addition to patching existing POUs, the code can create folders, POUs, DUTs, GVLs, actions, tasks, and mount POUs into tasks when missing, which is materially broader than the stated behavior.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
try:
if not os.path.exists(os.path.dirname(_err_early)):
os.makedirs(os.path.dirname(_err_early))
with open(_err_early, 'ab') as _fe:
_fe.write(u'[FATAL] workspace_dir \u672a\u8bbe\u7f6e\uff0c\u8bf7\u5148\u5728 env.json \u4e2d\u5199\u5165 workspace_dir \u5b57\u6bb5\n'.encode('utf-8'))
except:
pass
The nearby comments and docstring frame this section as applying TASK_MOUNTS to an existing Task Configuration. However, when create is enabled, the code will create a new task via task_cfg.create_task(task_name), which is a stronger side effect than merely applying mounts to existing tasks.
The script opens an existing automation project, mutates code objects and task configuration, then saves the project without any interactive confirmation, dry-run mode, or explicit guardrail at the point of overwrite. In an industrial-control/project-engineering context, silent persistence of unintended changes can cause integrity loss, operational disruption, and unsafe deployment if upstream inputs such as env.json, st/ content, or task_mounts.json are wrong or tampered with.
The launcher explicitly accepts an arbitrary absolute or relative path from the user and passes it to InoProShop via /runscript, which enables execution of any script reachable by the operator, not just the vetted aliases. In a skill context, this materially expands capability and bypasses any trust boundary implied by the built-in tool mapping, making accidental or unauthorized code execution much easier.
Allowing user-supplied script paths without an explicit safety warning increases the likelihood that operators will run unreviewed or unexpected code under the trusted InoProShop workflow. This is especially risky because the wrapper makes arbitrary script execution look equivalent to running the built-in maintenance aliases.
The script forcefully terminates all InoProShop processes by default unless -NoKill is specified, which can destroy unsaved work or interrupt unrelated sessions. Making destructive behavior the default is unsafe in shared or interactive environments because a normal invocation can unexpectedly affect other work on the host.
The skill description is presented in Chinese only, and the README does not offer any language or locale choice for users. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is clearly documented and justified.
The natural-language instructions and usage guidance are predominantly presented in Chinese, and the file does not indicate that this language choice is optional or region-specific. Under the language/locale policy, forcing a specific language without user opt-in can be a policy violation.
The module docstring presents usage, inputs, and outputs exclusively in Chinese. This can violate a language/locale policy when users are not given an explicit choice of language and the file does not document that it is intentionally region-specific.
The header documentation explicitly states that log/check_compile_result.txt is a single-line result of the form 'OK / FAIL:<错误数>'. However, on success the implementation writes 'OK:<warning_count> warnings' at L336, which contradicts the documented output contract rather than merely omitting detail.
The fatal error written to export_pou_error.txt is only in Chinese (workspace_dir 未设置,请先在 env.json 中写入 workspace_dir 字段). This imposes a fixed language choice on users without offering a locale option or documenting a justified region-specific constraint.