Back to skill

Security audit

Iflow Template Toolkit

Security checks for vulnerabilities and agentic risk

Overview

This skill is a small template/i18n toolkit, but its template-file loading can read files outside the intended template directory if given an unsafe path.

Review before installing if this toolkit will render templates selected by users or other untrusted input. It should be benign for trusted, fixed template names, but callers should map external template IDs to known files or add a containment check before using render_file/load_template in production.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
src/template_engine.py:24
Finding

Unrestricted Template Path Traversal

Content
View full analysis

Vulnerability Details

File Location: src/template_engine.py:24-32
Vulnerability Type: Path traversal leading to arbitrary local file read
Risk Level: Medium

Vulnerable Code

python
def load_template(self, template_name: str) -> str:
    if self.template_dir:
        template_path = self.template_dir / template_name
    else:
        template_path = Path(template_name)
    if not template_path.exists():
        raise FileNotFoundError(f"Template not found: {template_path}")
    with open(template_path, 'r', encoding='utf-8') as f:
        return f.read()

Technical Analysis

When template_dir is configured, load_template() directly joins the attacker-controllable template_name to that directory. It does not reject absolute paths, resolve traversal components, or verify that the resolved target remains inside the configured template root.

A value such as ../../sensitive-file can therefore escape the template directory. Depending on pathlib path-joining behavior, an absolute template_name may also replace the intended base directory. The resulting path is opened with the process's current filesystem privileges.

The same behavior is reachable through render_file(), which passes its template_name argument directly to load_template().

Attack Path

  1. An application creates TemplateEngine with a restricted template directory.
  2. The application permits an untrusted party to influence the template name passed to render_file() or load_template().
  3. The attacker submits a traversal path such as ../../path/to/readable/file.
  4. The engine joins that value to template_dir without a containment check.
  5. open() reads the escaped file if it exists and is readable by the process.
  6. The file contents are returned to the caller and may be exposed in rendered output, logs, API responses, or other downstream processing.

Impact Assessment

Successful exploitation permits reading arbitrary UTF-8-compatible ...[truncated 402 chars]

Remediation
View remediation

Remediation Suggestions

Resolve the configured template root and candidate path, then verify that the candidate is contained within the root before opening it. Explicitly reject absolute template names and consider allowlisting supported file extensions.

python
def load_template(self, template_name: str) -> str:
    supplied_path = Path(template_name)

    if supplied_path.is_absolute():
        raise ValueError("Absolute template paths are not allowed")

    if self.template_dir is None:
        raise ValueError("A template directory must be configured")

    root = self.template_dir.resolve()
    template_path = (root / supplied_path).resolve()

    try:
        template_path.relative_to(root)
    except ValueError:
        raise ValueError("Template path escapes the configured directory")

    if template_path.suffix not in {".md", ".txt", ".html"}:
        raise ValueError("Unsupported template file type")

    if not template_path.is_file():
        raise FileNotFoundError(f"Template not found: {template_path}")

    with template_path.open("r", encoding="utf-8") as f:
        return f.read()

Additionally:

  • Do not pass user-controlled template names directly to the engine.
  • Map external template identifiers to server-controlled filenames.
  • Run the application with least-privilege filesystem permissions.
  • Add tests covering ../ traversal, nested traversal, absolute paths, symlink escapes, nonexistent files, and valid in-root templates.
  • If symlinks are permitted inside the template directory, ensure resolution occurs before the containment check, as shown above.
Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The description advertises "multi-language support" and later examples hard-code specific locales, including Chinese, but the documentation does not explicitly state that language selection is user-controlled or optional. Because language/locale policy violations apply to natural-language content, this is a mild policy concern rather than a security flaw.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

This code hardcodes support for English and Chinese and exposes Chinese strings directly, but the file contains no natural-language guidance about when a Chinese locale should be used or whether the user chooses it. Because policy violations here are limited to language/locale constraints, this is only a mild concern and depends on how the broader skill selects languages.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · src/template_engine.py (reported line 53)May include surrounding context.

python
if isinstance(value, dict) and k in value:
                value = value[k]
            elif hasattr(value, k):
                value = getattr(value, k)
            else:
                return None
        return value

Static analysis

No suspicious patterns detected.