Back to skill

Security audit

Linux Journey

Security checks for vulnerabilities and agentic risk

Overview

The skill’s lesson recommendations are coherent, but its refresh script can fetch arbitrary URLs and write to arbitrary file paths, so it needs review before installation.

Install only if you are comfortable with a maintenance script that can make outbound requests and write files. Use the skill for reading the bundled lesson index and recommending public LabEx URLs; do not let untrusted prompts choose refresh script arguments, and refresh only with the default LabEx sitemap and intended references/lessons.md destination.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_lessons.py:21
Finding

Unrestricted URL Fetching Enables Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: scripts/fetch_lessons.py, lines 21–44
Vulnerability Type: Unrestricted outbound request / server-side request forgery
Risk Level: Medium

Vulnerable Code

python
parser.add_argument("--url", default=SITEMAP_URL, help="Sitemap URL to fetch.")
parser.add_argument(
    "--output",
    type=Path,
    default=Path(__file__).resolve().parents[1] / "references" / "lessons.md",
    help="Path to the generated Markdown file.",
)
parser.add_argument(
    "--include-locales",
    action="store_true",
    help="Include locale-specific lesson URLs instead of only canonical lesson URLs.",
)
return parser.parse_args()


def fetch_xml(url: str) -> bytes:
    req = urllib.request.Request(
        url,
        headers={
            "User-Agent": "CodexSkillBuilder/1.0 (+https://labex.io/linuxjourney-lessons-sitemap.xml)"
        },
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        return resp.read()

Technical Analysis

The command-line --url argument is passed directly to urllib.request.urlopen without validating the URL scheme, hostname, port, destination IP address, or redirect target. Although the default value references the expected LabEx sitemap, callers can replace it with an arbitrary URL.

This creates a server-side request forgery primitive in environments where an untrusted user can influence how the refresh script is invoked. The request executes from the Agent's network context, which may have access to localhost, private network services, cloud metadata endpoints, or other destinations that are not reachable by the attacker directly.

Redirect handling also requires consideration because urlopen can follow redirects. Validating only the initial URL would therefore be insufficient if an approved endpoint could redirect to a prohibited destination.

Attack Path

  1. An attacker causes the Agent or anot ...[truncated 1221 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the --url option if alternative sitemap sources are not a required feature.
  • Otherwise, parse the URL before making a request and require:
    • The https scheme.
    • The exact expected hostname, labex.io.
    • The expected sitemap path.
    • No embedded username or password.
    • No nonstandard port.
  • Disable redirects or validate every redirect destination against the same allowlist.
  • Resolve destination addresses and reject loopback, link-local, private, reserved, multicast, and unspecified address ranges where arbitrary hosts must be supported.
  • Apply a strict response-size limit while reading the body.
  • Keep the existing timeout and consider separate connection and read limits where supported.
  • Do not expose fetched response content or detailed internal network errors to untrusted users.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_lessons.py:47
Finding

Incomplete Lesson URL Validation Permits Recommendation Index Poisoning

Content
View full analysis

Vulnerability Details

File Location: scripts/fetch_lessons.py, lines 47–59 and 72–74
Vulnerability Type: Insufficient validation of externally sourced URLs
Risk Level: Medium

Vulnerable Code

python
def is_canonical_lesson_url(url: str) -> bool:
    parts = [part for part in urlparse(url).path.split("/") if part]
    return len(parts) == 2 and parts[0] == "lesson"


def extract_lessons(xml_bytes: bytes, include_locales: bool) -> list[tuple[str, str]]:
    root = ET.fromstring(xml_bytes)
    ns = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}
    lessons: list[tuple[str, str]] = []
    for loc in root.findall("sm:url/sm:loc", ns):
        url = (loc.text or "").strip()
        if not url:
            continue
        if not include_locales and not is_canonical_lesson_url(url):
            continue
        slug = url.rstrip("/").rsplit("/", 1)[-1]
        title = slug.replace("-", " ").title()
        lessons.append((title, url))
    lessons.sort(key=lambda item: item[0].lower())
    return lessons
python
for title, url in lessons:
    lines.append(f"- {title}")
    lines.append(f"  URL: {url}")

Technical Analysis

The is_canonical_lesson_url function validates only the parsed URL path. It does not verify the scheme, hostname, credentials, or port. Consequently, a URL such as https://attacker.example/lesson/linux-basics satisfies the path check despite not being hosted by LabEx.

URLs extracted from the externally retrieved XML are subsequently written to references/lessons.md. The Skill documentation directs the Agent to treat that file as the canonical source for recommendations. Poisoned entries can therefore cross a trust boundary from an external XML source into Agent-generated user recommendations.

When --include-locales is enabled, the canonical URL check is skipped entirely, making validation even weaker.

Attack Path

  1. An a ...[truncated 1166 chars]
Remediation
View remediation

Remediation Suggestions

  • Validate every extracted URL, including locale-specific URLs.
  • Require all of the following before adding a lesson:
    • scheme == "https".
    • hostname == "labex.io".
    • No embedded credentials.
    • No fragment.
    • No unexpected port.
    • A path matching the exact supported lesson pattern.
  • Reject protocol-relative URLs and non-HTTP schemes.
  • Define and validate the permitted locale path format instead of bypassing validation when --include-locales is enabled.
  • Normalize the hostname before comparison and use an exact comparison rather than a suffix check.
  • Consider reconstructing the final URL from a validated slug and a fixed trusted origin rather than preserving the complete source URL.
  • Verify the generated index before replacing the existing canonical file.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_lessons.py:22
Finding

Unrestricted Output Path Allows Overwriting Arbitrary Writable Files

Content
View full analysis

Vulnerability Details

File Location: scripts/fetch_lessons.py, lines 22–27 and 87–89
Vulnerability Type: Arbitrary writable-file overwrite
Risk Level: Medium

Vulnerable Code

python
parser.add_argument(
    "--output",
    type=Path,
    default=Path(__file__).resolve().parents[1] / "references" / "lessons.md",
    help="Path to the generated Markdown file.",
)
python
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(render_markdown(lessons, args.url), encoding="utf-8")
print(f"Wrote {len(lessons)} lessons to {args.output}")

Technical Analysis

The script accepts an unrestricted filesystem path through --output. It then creates missing parent directories and overwrites the destination using Path.write_text. There is no check that the resolved destination remains inside the project directory or the intended references directory.

The implementation also does not reject symbolic links. If the process has permission to follow a link and write its target, an attacker who can prepare the output location may redirect the write to another file.

The generated content is Markdown rather than arbitrary attacker-selected bytes, which limits direct payload control. Nevertheless, replacing configuration, state, documentation, or other parseable files can cause corruption or potentially influence downstream systems that consume the overwritten file.

Attack Path

  1. An attacker influences the refresh command and supplies --output with an absolute path, a traversal path, or a path containing a malicious symbolic link.
  2. The script creates any missing parent directories that the current process is allowed to create.
  3. write_text follows normal filesystem resolution and replaces the destination content.
  4. The target file is overwritten with generated Markdown.
  5. A downstream application may fail, load corrupted state, or interpret parts of ...[truncated 671 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the --output option if the generated file has a fixed destination.
  • Resolve both the intended base directory and requested destination with Path.resolve.
  • Verify that the resolved destination is the expected file or is strictly contained within the approved references directory.
  • Reject absolute paths, traversal outside the approved directory, and unexpected filenames.
  • Reject symbolic-link destinations and symbolic-link parent components.
  • Write to a securely created temporary file in the same approved directory, flush it, and atomically replace the intended destination.
  • Run the script with the minimum filesystem permissions needed to update the lesson index.
  • Avoid creating arbitrary parent directories; create only the known application-owned output directory.
Vulnerability Patterns
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

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 35)May include surrounding context.

md
- If multiple lessons look similar, prefer the more foundational lesson first.
- If no exact topic match is obvious, provide the closest Linux Journey lessons and say they are the nearest available matches.

## Output Rules

- Keep the answer short and practical.
- Prefer URL-first recommendations.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill instructs the agent to use local files and potentially refresh content via scripts/fetch_lessons.py, which implies file-write and network-capable behavior, but it declares no explicit tool scope or permission boundaries. Without an allowlist, an agent runtime may grant broader-than-necessary capabilities, increasing the risk of unintended file modification or network access if the skill is invoked in a broader context.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.