Back to skill

Security audit

Frontend Cv

Security checks for vulnerabilities and agentic risk

Overview

This resume skill is mostly purpose-aligned, but it under-discloses privacy and browser risks around generated resume files that may contain personal information.

Review before installing if you handle sensitive resumes. Use a virtual environment, avoid running it on untrusted resume/YAML files, consider removing remote font links or working offline, and confirm where generated resume_data and preview files will be stored before opening or sharing the HTML.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render_html.py:108
Finding
Unvalidated Social-Network URI Schemes Allow Executable Links<![CDATA[ ## Vulnerability Details **File Location**: `scripts/render_html.py:108-113` and `scripts/render_html.py:151-160` **Vulnerability Type**: Unsafe URI handling in generated HTML **Risk Level**: Medium ### Vulnerable Code ```python def social_link(network: str, username: str) -> str: if network == "GitHub": return f"https://github.com/{username}" if network == "LinkedIn": return f"https://linkedin.com/in/{username}" return username ``` The returned value is subsequently inserted into an anchor: ```python tag = "a" if item["href"] else "span" href = f' href="{esc(item["href"])}"' if item["href"] else "" rendered.append( f'<{tag} class="connection connection--{esc(item["kind"])}"{href}>{icon}<span>{esc(item["label"])}</span></{tag}>' ) ``` ### Technical Analysis For recognized GitHub and LinkedIn entries, the renderer constructs an HTTPS URL. For every other network name, however, `social_link()` returns the user-controlled `username` unchanged. The `esc()` function applies HTML entity encoding, which prevents breaking out of the `href` attribute, but it does not validate the URI scheme. Consequently, values using schemes such as `javascript:` can remain executable when the generated link is clicked. For example, the following resume data can create an executable link: ```yaml social_networks: - network: Custom username: "javascript:alert(document.domain)" ``` URI validation must be performed independently of HTML escaping. ### Attack Path 1. An attacker supplies or modifies a resume YAML file. 2. The attacker adds an unrecognized social-network name. 3. The corresponding `username` contains a `javascript:` URI or another unsafe scheme. 4. The user invokes `scripts/render_html.py` on that YAML file. 5. The renderer places the value into an anchor's `href` attribute. 6. The generated resume is opened or shared with another user. 7. If the malicious link is clicked, the browser executes the URI in the generate ...[truncated 552 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Parse every generated link using `urllib.parse.urlparse`. - Permit only an explicit scheme allowlist, such as `https` and `http` for websites and social profiles. - Handle `mailto` and `tel` only in their dedicated contact fields. - Reject `javascript`, `data`, `file`, `vbscript`, and all unknown schemes. - Maintain an explicit map of supported social networks rather than returning unknown usernames as links. - Render unsupported social-network values as plain text. - Add regression tests covering mixed-case and whitespace-obfuscated schemes, including `JaVaScRiPt:`, leading control characters, and percent-encoded variants. A safe pattern would be: ```python from urllib.parse import urlparse ALLOWED_WEB_SCHEMES = {"https", "http"} def validate_web_url(value: str) -> str: normalized = normalize_url(value) parsed = urlparse(normalized) if parsed.scheme.lower() not in ALLOWED_WEB_SCHEMES: return "" return normalized ``` ]]>

other

Note
Location
scripts/render_html.py:884
Finding
Generated Resumes Load Undisclosed Remote Font Resources<![CDATA[ ## Vulnerability Details **File Location**: `references/themes/classic.yaml:5-7`, `references/themes/engineeringclassic.yaml:5-7`, `references/themes/engineeringresumes.yaml:5-7`, `references/themes/modern.yaml:5-7`, `references/themes/sb2nov.yaml:5-7`, and `scripts/render_html.py:884-890` **Vulnerability Type**: External resource loading and privacy exposure **Risk Level**: Low ### Vulnerable Code Each theme includes a remote Google Fonts stylesheet. For example: ```yaml fonts: links: - https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&family=Noto+Sans+SC:wght@400;500;700&display=swap ``` The renderer inserts these URLs into generated documents: ```python font_links = theme["fonts"].get("links", []) fonts_markup = "\n".join( f' <link rel="stylesheet" href="{esc(link)}">' for link in font_links ) ``` The resulting markup is placed in the document head: ```html <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=..."> ``` ### Technical Analysis Opening a generated resume causes the browser to contact Google Fonts and related font-serving infrastructure. These requests disclose connection metadata such as the viewer's IP address, user agent, request time, and potentially referrer information, depending on browser behavior. This behavior conflicts with the project's claims that generated documents are self-contained and work offline. Although the Python renderer does not itself retrieve or execute a remote code payload, the generated artifact depends on external resources at viewing time. The configured font URLs are static Google Fonts endpoints rather than attacker-controlled URLs under the bundled themes. Therefore, this finding is a privacy and deployment-integrity concern rather than confirmed remote code execution. ### Attack Path 1. A user generates a resume using any bundled theme. 2. The renderer inserts an external stylesheet link into the output HTML. 3. The user sends the resume H ...[truncated 680 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove external stylesheet links from the default generated output. - Prefer local system font stacks when fully self-contained output is required. - If custom fonts are necessary, bundle reviewed font files and embed them through `@font-face` using local or `data:` resources. - Make remote-font loading an explicit opt-in option. - Clearly disclose any external requests before generating or sharing the document. - Add a Content Security Policy suitable for local resumes, such as restricting `default-src` and `style-src` to local content. - Update the documentation so that claims of offline and self-contained operation accurately reflect the implementation. ]]>

T08 · Insecure Dependencies

Note
Location
README.md:116
Finding
Unpinned and Inconsistent Python Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `README.md:116-123` and `scripts/extract_resume.py:8-17,20-28` **Vulnerability Type**: Unpinned third-party dependencies and inconsistent package guidance **Risk Level**: Low ### Vulnerable Code The documentation instructs users to install mutable latest versions without hashes: ```bash pip install PyPDF2 python-docx pyyaml jinja2 ``` The extraction script imports a different PDF package and recommends another unpinned installation: ```python def extract_pdf(file_path): """Extract text from PDF""" try: import pypdf with open(file_path, 'rb') as f: reader = pypdf.PdfReader(f) text = '\n\n'.join(page.extract_text() for page in reader.pages) return text except ImportError: print("Error: pypdf not installed. Run: pip install pypdf", file=sys.stderr) sys.exit(1) ``` The DOCX dependency is similarly unpinned: ```python def extract_docx(file_path): """Extract text from DOCX""" try: import docx doc = docx.Document(file_path) text = '\n\n'.join(para.text for para in doc.paragraphs if para.text.strip()) return text except ImportError: print("Error: python-docx not installed. Run: pip install python-docx", file=sys.stderr) sys.exit(1) ``` ### Technical Analysis Installing dependencies without fixed versions or integrity hashes makes the environment non-reproducible. Users will receive whichever releases the package index serves at installation time, including future releases that have not been audited with this project. There is also a direct inconsistency between the README, which installs `PyPDF2`, and the implementation, which imports `pypdf`. The documented `jinja2` dependency is not used by the reviewed scripts. This mismatch encourages additional installation attempts and unnecessarily expands the dependency surface. This does not establish that any listed package is cu ...[truncated 1212 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Provide a dependency lock file with exact, reviewed versions. - Use hashes through a requirements file compatible with `pip install --require-hashes`. - Choose one PDF package and use it consistently; because the script imports `pypdf`, the documentation should specify the reviewed `pypdf` version. - Remove `jinja2` and any other unused dependencies. - Separate optional PDF and DOCX dependencies from the core renderer dependencies. - Recommend installation inside an isolated virtual environment rather than a global or privileged environment. - Add automated dependency scanning and scheduled review of pinned versions. - Document a controlled upgrade process that updates pins only after compatibility and security testing. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description promises a broad resume-processing pipeline: ingest various formats, extract resume data, structure it as YAML, render styled HTML with theme options, and export to PDF. The actual code chunk is much narrower. It is a CLI extractor that reads supported file types and outputs plain extracted text. While text extraction is one supporting part of the declared workflow, the chunk does not implement the major advertised capabilities such as YAML structuring, HTML generation, theming, or PDF creation. Therefore the description materially overstates what this code actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code chunk is specifically an HTML renderer for resume data already stored in YAML. It reads YAML, loads a theme YAML, builds CSS/HTML, and writes an HTML file. It does support multiple themes, which aligns with part of the description. However, several major declared capabilities are absent in this code: there is no handling of markdown, PDF, Word, or text inputs; no extraction of resume information from arbitrary documents; no conversion pipeline into structured YAML; and no PDF generation/export. Because the declared purpose describes a broader end-to-end resume conversion and export tool, while the actual code only performs the HTML rendering subset, this is a material description-behavior mismatch.

Session Persistence

Medium
Category
Rogue Agent
Content
Copy the skill files to your Claude Code skills directory:

```bash
# Create the skill directory
mkdir -p ~/.claude/skills/frontend-cv/scripts

# Copy all files
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Skill Enumeration

Medium
Category
Agent Snooping
Content
```bash
# Create the skill directory
mkdir -p ~/.claude/skills/frontend-cv/scripts

# Copy all files
cp SKILL.md theme-presets.md html-template.md print-styles.css ~/.claude/skills/frontend-cv/
Confidence
85% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The README presents "Help me create a resume" as a way to use the skill, but that phrasing is broad natural language rather than a narrowly scoped trigger description. Without negative examples or clearer constraints, it could overlap with ordinary conversation and make invocation boundaries less clear.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill instructs the agent to read and write local files and to open generated HTML in a browser, but it does not declare any explicit tool scope or permissions boundaries. That increases the chance of unintended file access, uncontrolled file writes, or browser/network activity beyond what a user reasonably expects from a resume-formatting task.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill processes highly sensitive resume files that commonly contain PII such as full name, phone number, email, location, employment history, and education, yet it gives no privacy notice, retention guidance, or warning about where extracted content will be stored. This can lead to accidental exposure of personal data in intermediate files like resume_data.txt or resume_data.yaml.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The cleanup step instructs deletion of the preview directory without warning or confirmation, which is risky because generated files may contain user-edited content or be useful for later comparison. Silent deletion undermines user control and can result in unintentional data loss.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The shared template sets `lang="zh-CN"` and uses Chinese title/content text in the default shell, which imposes a specific language/locale on all consumers of this template. The file does not indicate that this is optional, user-selectable, or limited to a China-specific use case, so it can violate language/locale policy expectations.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The renderer emits <link rel="stylesheet"> tags directly from theme-controlled font_links values into the generated HTML with only HTML escaping, not origin validation or scheme restrictions. If an attacker can influence theme YAML or supply a malicious theme, opening the generated resume will trigger network requests to attacker-controlled URLs, enabling tracking, privacy leakage, and potentially unsafe rendering behavior depending on the HTML-to-PDF/browser environment.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The generated document sets `lang="zh-CN"` unconditionally, which forces a specific locale for all outputs regardless of user preference or resume content. This is a natural-language policy concern because the file also uses mixed language behavior elsewhere, but provides no opt-in or configuration for locale selection.

Missing User Warnings

Low
Confidence
84% confidence
Finding
Automatically opening generated preview HTML files can trigger unexpected side effects, including browser access to linked remote fonts or other embedded resources, and may surprise users in constrained or privacy-sensitive environments. While lower severity, undisclosed automatic launch is still an impactful action that should be explicit and user-approved.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
This markdown file contains several user-facing 'Best for' descriptions in Chinese while the surrounding document is in English. Because the file does not explain that it is intended only for Chinese-speaking users or offer an alternative language/locale choice, it may violate the language/locale policy for natural-language content.

Static analysis

No suspicious patterns detected.