Back to skill

Security audit

ELPA

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but its training runner turns configuration into unrestricted local shell commands with broad environment access.

Install only if you will run it with configs you fully trust. Treat training configs as executable code, review every train_cmd and placeholder value before using --execute, avoid running it with sensitive API keys in the environment, and use an isolated workspace or container for training runs.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/elpa_orchestrator.py:56
Finding
Shell Command Injection Through Configuration-Derived Commands and Placeholder Values<![CDATA[ ## Vulnerability Details **File Location**: `scripts/elpa_orchestrator.py`, lines 56-61, 92-95, and 139-147 **Vulnerability Type**: OS command injection caused by unsafe shell execution **Risk Level**: High ### Vulnerable Code ```python def _render_command(template: str, context: Dict[str, Any]) -> str: try: return template.format(**context) except KeyError as exc: missing = str(exc).strip("'") raise ValueError(f"missing placeholder '{missing}' in context for command: {template}") from exc ``` ```python context = _build_context(cfg, run_dir=run_dir, model_dir=model_dir, model_name=name) command = _render_command(cmd_template, context) ``` ```python completed = subprocess.run( item["train_cmd"], shell=True, cwd=item["model_dir"], env=env, stdout=out_f, stderr=err_f, check=False, ) ``` ### Technical Analysis The orchestrator reads `train_cmd` templates and placeholder values from a JSON configuration, combines them using Python string formatting, and submits the resulting string to `subprocess.run()` with `shell=True`. Because the generated command is interpreted by a command shell, shell metacharacters in either the template or substituted values retain their special meaning. A value intended to represent only a dataset path, project path, interpreter, or other argument can therefore introduce additional commands through characters such as `;`, `|`, command substitution, or redirection. For example, a configuration-derived dataset value conceptually equivalent to: ```text /path/to/data.csv; attacker-command ``` would be inserted directly into the command string. When execution is enabled, the shell interprets the text after the semicolon as another command. The documented design intentionally permits users to specify training commands. Nevertheless, the implementation does not distinguish executable command structure from data-only placeholder values. Consequently, configurations ob ...[truncated 1659 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace free-form shell command strings with structured argument arrays in the configuration. For example: ```json { "train_args": [ "{python_bin}", "{project_root}/model/train.py", "--data", "{dataset}", "--save-dir", "{model_dir}" ] } ``` 2. Render each argument independently and execute it without a shell: ```python completed = subprocess.run( rendered_args, shell=False, cwd=item["model_dir"], env=env, stdout=out_f, stderr=err_f, check=False, ) ``` 3. Validate data-only placeholders according to their intended types. Paths should be parsed as paths, numeric fields should remain numeric, and executable paths should be selected from an approved configuration or allowlist. 4. Do not attempt to make shell execution safe solely by applying generic quoting after command construction. Separating arguments and using `shell=False` provides a stronger security boundary. 5. If shell syntax is an unavoidable product requirement, explicitly document the configuration as trusted executable code, reject untrusted configurations, display a prominent confirmation before execution, and validate the configuration's provenance or signature. 6. Consider restricting environment-variable overrides, especially security-sensitive variables such as `PATH`, `PYTHONPATH`, and loader-related variables, when configurations are not fully trusted. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/elpa_orchestrator.py:76
Finding
Path Traversal Through Unsanitized Model Names<![CDATA[ ## Vulnerability Details **File Location**: `scripts/elpa_orchestrator.py`, lines 76-102 and 133-147 **Vulnerability Type**: Path traversal and unintended file creation or truncation **Risk Level**: Medium ### Vulnerable Code ```python name = str(raw.get("name", "")).strip() group = str(raw.get("group", "online")).strip().lower() cmd_template = str(raw.get("train_cmd", "")).strip() enabled = bool(raw.get("enabled", True)) env = raw.get("env", {}) if not name: raise ValueError("model 'name' is required") if group not in {"online", "offline"}: raise ValueError(f"model '{name}' has invalid group '{group}', expected online/offline") if not cmd_template: raise ValueError(f"model '{name}' missing train_cmd") if env and not isinstance(env, dict): raise ValueError(f"model '{name}' env must be object if provided") model_dir = run_dir / "models" / name model_dir.mkdir(parents=True, exist_ok=True) context = _build_context(cfg, run_dir=run_dir, model_dir=model_dir, model_name=name) command = _render_command(cmd_template, context) items.append( { "name": name, "group": group, "enabled": enabled, "train_cmd_template": cmd_template, "train_cmd": command, "model_dir": str(model_dir), "env": {str(k): str(v) for k, v in env.items()} if env else {}, "status": "planned" if enabled else "disabled", "return_code": None, "stdout_log": str(model_dir / "train.stdout.log"), "stderr_log": str(model_dir / "train.stderr.log"), ``` ```python stdout_path = Path(item["stdout_log"]) stderr_path = Path(item["stderr_log"]) stdout_path.parent.mkdir(parents=True, exist_ok=True) stderr_path.parent.mkdir(parents=True, exist_ok=True) with stdout_path.open("w", encoding="utf-8") as out_f, stderr_path.open("w", encoding="utf-8") as err_f: completed = subprocess.run( item["train_cmd"], shell=True, cwd=item["model_dir"], env=env, stdout=ou ...[truncated 2618 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict model names to identifiers rather than paths. For example: ```python import re if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", name): raise ValueError(f"model '{name}' contains invalid characters") if name in {".", ".."}: raise ValueError("model name cannot be '.' or '..'") ``` 2. Resolve both the model root and candidate path, then enforce containment before creating directories or files: ```python model_root = (run_dir / "models").resolve() model_dir = (model_root / name).resolve() if model_dir == model_root or model_root not in model_dir.parents: raise ValueError("model directory escapes the configured model root") ``` 3. Apply the containment check before calling `mkdir()`, opening log files, or using the path as a working directory. 4. Avoid following attacker-controlled symbolic links where configurations or run directories may be writable by other users. Use a run root with restrictive ownership and permissions, and consider descriptor-based or no-follow file APIs for stronger protection. 5. Open logs using safer creation semantics when overwriting is unnecessary. Unique filenames or exclusive creation can reduce accidental truncation: ```python stdout_path.open("x", encoding="utf-8") ``` 6. Add regression tests covering `../`, nested traversal, absolute paths, `.` and `..`, path separators, and symlink-based containment escapes. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the skill claims ELPA integration behavior but in practice exposes undeclared arbitrary shell command execution through configurable `train_cmd`, that is a meaningful security issue. The dangerous part is not the description mismatch alone, but that users may trust a forecasting utility while it can launch arbitrary external commands from configuration, creating command-execution and supply-chain risk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
If the skill claims ELPA integration behavior but in practice exposes undeclared arbitrary shell command execution through configurable `train_cmd`, that is a meaningful security issue. The dangerous part is not the description mismatch alone, but that users may trust a forecasting utility while it can launch arbitrary external commands from configuration, creating command-execution and supply-chain risk.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
item["status"] = "running"
        item["started_at"] = _now()
        env = os.environ.copy()
        env.update(item.get("env", {}))

        stdout_path = Path(item["stdout_log"])
Confidence
91% confidence
Finding
Copying the full parent process environment into child training jobs propagates all available secrets and credentials, such as API keys, cloud tokens, and internal service configuration, to externally defined sub-model commands. In this skill, those commands are intentionally configurable and may invoke arbitrary frameworks or scripts, so environment inheritance materially increases the blast radius of any malicious or compromised training command.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
stderr_path.parent.mkdir(parents=True, exist_ok=True)

        with stdout_path.open("w", encoding="utf-8") as out_f, stderr_path.open("w", encoding="utf-8") as err_f:
            completed = subprocess.run(
                item["train_cmd"],
                shell=True,
                cwd=item["model_dir"],
Confidence
99% confidence
Finding
This is a tool-parameter abuse issue because the subprocess invocation accepts a fully attacker-controllable command string from model configuration and executes it through the shell. The skill's purpose is to orchestrate external training jobs, which makes this especially dangerous: the code effectively turns configuration into code execution without validation or sandboxing.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises and documents shell-based orchestration, file reads/writes, and likely environment use, but it declares no explicit tool scope or permission boundaries. In a skill that executes user-supplied `train_cmd` values, missing permission declarations materially increases the risk of arbitrary command execution, filesystem modification, and abuse of ambient credentials or hardware resources.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
stderr_path.parent.mkdir(parents=True, exist_ok=True)

        with stdout_path.open("w", encoding="utf-8") as out_f, stderr_path.open("w", encoding="utf-8") as err_f:
            completed = subprocess.run(
                item["train_cmd"],
                shell=True,
                cwd=item["model_dir"],
Confidence
99% confidence
Finding
The orchestrator builds a shell command from configuration data and then executes it with shell=True. Because train_cmd is derived from JSON config and template-expanded context values, an attacker who can influence the config can inject arbitrary shell metacharacters or additional commands, leading to arbitrary command execution on the host.

Scope Creep

Low
Category
Excessive Agency
Content
- Put your real training entrypoints in each model `train_cmd`.
- Keep each model tagged as `online` or `offline`.
- Add as many models as needed; ELPA is not limited to 4.

## 2) Dry-Run Plan (No Training)
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Static analysis

No suspicious patterns detected.