Back to skill

Security audit

identify-rd-directions-rd

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent report-generation purpose, but its bundled renderer has file-writing and Markdown-safety flaws that should be reviewed before installation.

Review this skill before installing in automated or privileged environments. If used, run it only in a trusted output directory that untrusted users cannot write to, avoid --overwrite unless the exact target files are intended, and open generated Markdown with a sanitizing viewer. The publisher should replace the deterministic .tmp write with exclusive secure temporary-file handling and fully neutralize payload-derived Markdown constructs.

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/render_report.py:1703
Finding

Predictable Temporary File Allows Symlink-Based Arbitrary File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/render_report.py, lines 1703–1711
Vulnerability Type: Predictable temporary file and symlink-following file write
Risk Level: High

Vulnerable Code

python
def write_output(path: Path, content: str, overwrite: bool) -> None:
    if path.exists() and not overwrite:
        fail(f"Refusing to overwrite existing file without --overwrite: {path}")
    if path.exists() and path.is_dir():
        fail(f"Output path is a directory: {path}")
    path.parent.mkdir(parents=True, exist_ok=True)
    temporary = path.with_name(path.name + ".tmp")
    temporary.write_text(content, encoding="utf-8", newline="\n")
    temporary.replace(path)

Technical Analysis

The renderer constructs a deterministic temporary pathname by appending .tmp to the output filename. It neither creates this file exclusively nor verifies that it is a regular file rather than a symbolic link.

Python's Path.write_text() follows symbolic links. An attacker who can write to the output directory can therefore create the predictable temporary path as a symbolic link to another file before rendering begins. When the renderer writes the generated report, it follows the link and truncates or replaces the target file's contents using the renderer process's privileges.

The subsequent temporary.replace(path) renames the temporary directory entry, which may itself be the attacker-created symlink, over the intended report path. Consequently, both unintended target-file corruption and an unexpected symlink at the final output location may occur.

This behavior contradicts the documented path contract, which states that symbolic-link targets must be refused.

Attack Path

  1. Identify or influence the report output path, such as /shared/reports/report.html.
  2. Obtain write access to its parent directory.
  3. Create the predictable temporary pathname as a symboli ...[truncated 1349 chars]
Remediation
View remediation

Remediation Suggestions

  • Replace the deterministic .tmp pathname with a securely generated temporary file in the destination directory using tempfile.mkstemp() or tempfile.NamedTemporaryFile(delete=False).
  • Use exclusive creation semantics so an existing file or symlink cannot be reused.
  • Verify the output path, temporary path, and relevant parent directories with lstat() rather than checks that follow symbolic links.
  • Refuse output paths that are symbolic links, including dangling symbolic links.
  • Write and flush the content through the securely opened file descriptor, optionally call os.fsync(), and then use os.replace() for atomic installation.
  • Apply restrictive permissions to the temporary file.
  • Remove the temporary file in a finally block if validation, writing, or replacement fails.
  • Where output directories are shared, ensure they are not writable by untrusted users.
  • Add regression tests that pre-create both the output and temporary paths as symbolic links and verify that rendering fails without modifying their targets.

A secure implementation should follow this pattern:

python
import os
import tempfile

def write_output(path: Path, content: str, overwrite: bool) -> None:
    if path.is_symlink():
        fail(f"Output path must not be a symbolic link: {path}")
    if path.exists() and not overwrite:
        fail(f"Refusing to overwrite existing file without --overwrite: {path}")
    if path.exists() and path.is_dir():
        fail(f"Output path is a directory: {path}")

    path.parent.mkdir(parents=True, exist_ok=True)
    fd, temporary_name = tempfile.mkstemp(
        prefix=f".{path.name}.",
        suffix=".tmp",
        dir=path.parent,
        text=True,
    )
    temporary = Path(temporary_name)
    try:
        with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
            handle.write(content)
            handle.flush()
    
...[truncated 158 chars]

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render_report.py:647
Finding

Untrusted Payload Content Is Emitted as Active Markdown and Raw HTML

Content
View full analysis

Vulnerability Details

File Location: scripts/render_report.py, lines 647–662 and 741–779
Vulnerability Type: Stored Markdown and raw-HTML content injection
Risk Level: Medium

Vulnerable Code

python
def markdown_escape(value: Any) -> str:
    rendered = str(value) if value is not None else ""
    return rendered.replace("\\", "\\\\").replace("|", "\\|")


def markdown_link(label: Any, url: str) -> str:
    safe_label = str(label).replace("[", "\\[").replace("]", "\\]")
    if not url:
        return safe_label + " — Source link not supplied"
    safe_target = url.replace(" ", "%20").replace(")", "%29")
    return f"[{safe_label}]({safe_target})"

Payload fields are subsequently interpolated directly into the Markdown report:

python
def build_markdown(
    payload: dict[str, Any],
    maps: dict[str, dict[str, dict[str, Any]]],
) -> str:
    meta = payload["meta"]
    analysis = payload["analysis"]
    counts = derived_counts(payload, maps)
    appendix = partition_evidence(maps["evidence"])
    lines: list[str] = ["# R&D Direction Evidence Report", ""]
    if meta["project_name"].strip():
        lines.append(f"> **Project:** {meta['project_name']}")
    if meta["applicant_or_team"].strip():
        lines.append(f"> **Applicant or team:** {meta['applicant_or_team']}")
    lines.extend(
        [
            f"> **Report date:** {meta['report_date']}",
            f"> **Evidence cutoff:** {meta['evidence_cutoff']}",
            f"> **Scope:** {meta['scope']}",
            f"> **Geographies:** {join_values(meta['geographies'])}",
            f"> **Languages:** {join_values(meta['languages'])}",
            f"> **Patent count unit:** {meta['patent_count_unit']}",
            "",
            "## Source Requirement",
            "",
            *["> " + line for line in payload["requirement_text"].splitlines()],
            "",
 
...[truncated 2935 chars]
Remediation
View remediation

Remediation Suggestions

  • Treat every payload string as untrusted when generating Markdown.
  • Define context-specific escaping for plain text, table cells, blockquotes, headings, link labels, and link destinations.
  • Reject or encode raw HTML tags in payload-derived text.
  • Prevent payload text from introducing new block-level Markdown structures through embedded line breaks or line-leading control characters.
  • Allow links only through the existing validated URL fields; render ordinary text fields so they cannot create links or images.
  • Apply a Markdown sanitization policy that blocks embedded images, inline HTML, dangerous URI schemes, and viewer-specific active constructs.
  • Add a post-render Markdown safety validator rather than checking only for required section fragments.
  • Test payloads containing raw HTML, image syntax, nested links, headings, blockquote escapes, and multiline table-cell content.
  • If rich Markdown from payload fields is not required, encode Markdown metacharacters and HTML-sensitive characters so all payload values render strictly as text.

The renderer should also document the supported Markdown safety profile and warn users not to open legacy artifacts in viewers that permit unsanitized active HTML.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (9)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 42)May include surrounding context.

md
- `scripts/render_report.py --help` before rendering.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 418)May include surrounding context.

md
- `scripts/render_report.py --help` before rendering.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · SKILL.md (reported line 464)May include surrounding context.

md
- missing URLs are plain text;
- no external CSS, font, script, image, or runtime request;
- no gradients, emoji navigation, domestic fonts, raw unsafe HTML, analytics, or storage;
- responsive and print rules present;
- source register and limitations visible.

Check filesystem:

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · scripts/render_report.py (reported line 1705)May include surrounding context.

python
def write_output(path: Path, content: str, overwrite: bool) -> None:
    if path.exists() and not overwrite:
        fail(f"Refusing to overwrite existing file without --overwrite: {path}")
    if path.exists() and path.is_dir():
        fail(f"Output path is a directory: {path}")
    path.parent.mkdir(parents=True, exist_ok=True)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill explicitly instructs reading local files, writing artifacts, invoking a renderer, and performing external searches, but it does not declare an explicit tool/permission scope. That creates an authorization ambiguity: an agent runtime may grant broader file or network access than a user expects, increasing the chance of unintended data exposure, overwrites, or unreviewed external transmission.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The instruction 'Use the current callable schema and English interface' imposes a specific language choice in natural-language guidance. Elsewhere the skill records user languages and multilingual search scope, but this line does not present English as optional or justified by a region-specific constraint.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The checklist mandates HTML with 'lang="en"', which forces an English locale in the final artifact. The file does not frame this as user-selected or region-specific, so it conflicts with the policy against imposing a fixed language/locale without opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The instruction 'Use the current callable schema and English interface' imposes a specific language requirement in natural language. The file does not indicate user opt-in or explain a region- or tool-specific necessity for forcing English, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code emits <html lang="en"> unconditionally, which forces the rendered report to declare English regardless of the payload's meta.languages field or user preference. Under the language/locale policy rule, a fixed language setting without opt-in or justification is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.