Back to skill

Security audit

GitHub to RedNote

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but its default caching of authenticated GitHub data and unsafe cover-image SVG handling deserve review before installation.

Install only if you are comfortable giving it a least-privilege GitHub token and storing fetched repository data locally. Avoid using it on private repositories unless caching is disabled or isolated, and avoid --with-image for untrusted or unusual GitHub URLs until the SVG escaping issue is fixed.

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/image_generator.py:297
Finding
Unescaped Repository URL Enables SVG/XML Injection During Cover Generation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/image_generator.py:194-199`, `scripts/image_generator.py:297-300`, with rendering sinks at `scripts/image_generator.py:328-330`, `scripts/image_generator.py:343-348`, and `scripts/image_generator.py:360-365` **Vulnerability Type**: SVG/XML injection through unescaped user-controlled data **Risk Level**: High ### Vulnerable Code ```python repo_name = repo_data.get('repo', 'Unknown') description = repo_data.get('description', '') language = repo_data.get('language', 'Unknown') or 'Unknown' stars = repo_data.get('stars', 0) github_url = repo_data.get('url', '') or f"github.com/{repo_data.get('owner', '')}/{repo_name}" # Escape XML special chars display_name = escape_xml(truncate_text(repo_name, 22)) desc_text = escape_xml(truncate_text(description, 80)) lang_text = escape_xml(language) ``` The repository URL is subsequently inserted into the SVG without XML escaping: ```python <!-- GitHub URL --> <text x="540" y="1220" font-family="Courier New, monospace" font-size="20" fill="{accent_color}" text-anchor="middle"> {github_url} </text> ``` The generated SVG is then processed by one of several renderers: ```python cairosvg.svg2png( url=svg_path, write_to=output_path, output_width=COVER_WIDTH, output_height=COVER_HEIGHT ) ``` ```python result = subprocess.run( ['convert', '-background', 'none', svg_path, '-resize', f'{COVER_WIDTH}x{COVER_HEIGHT}!', '-density', '150', output_path], capture_output=True, text=True, timeout=30, env=env ) ``` ```python result = subprocess.run( ['inkscape', svg_path, '--export-type=png', f'--export-filename={output_path}', f'--export-width={COVER_WIDTH}', f'--export-height={COVER_HEIGHT}', '--export-dpi=150'], capture_output=True, text=True, timeout=30 ) ``` ### Technical Analysis The `github_url` value originates from the user-supplied command-line URL and is retained as `repo_data['url']`. Unlike ...[truncated 2728 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value inserted into SVG XML, including the URL: ```python github_url_text = escape_xml( truncate_text( f"github.com/{repo_data.get('owner', '')}/{repo_name}", 100 ) ) ``` 2. Do not display the original user-supplied URL. Reconstruct the display URL exclusively from validated owner and repository components returned by the GitHub API. 3. Tighten URL validation so that only these forms are accepted: ```text https://github.com/<owner>/<repository> github.com/<owner>/<repository> <owner>/<repository> ``` Reject query strings, fragments, control characters, XML metacharacters, credentials, and unexpected trailing path components. 4. Use a standard URL parser rather than permissive regular expressions. Validate the hostname exactly as `github.com`. 5. Configure SVG renderers to prohibit: - External network resources. - Local-file references. - Script execution. - Unsafe XML entities. - Unbounded filters, dimensions, and resource consumption. 6. Run image conversion in a sandbox with: - No network access. - A restricted temporary directory. - CPU, memory, file-size, and execution-time limits. - No access to sensitive home-directory files. 7. Add regression tests containing XML metacharacters and attempted element injection in every repository field. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/github_api.py:97
Finding
Authenticated GitHub Responses Are Cached Without Explicit Owner-Only Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/github_api.py:50-57` and `scripts/github_api.py:97-103` **Vulnerability Type**: Insecure storage of potentially private authenticated repository data **Risk Level**: Medium ### Vulnerable Code The cache directory is created without explicitly setting restrictive permissions: ```python if cache_dir is None: cache_dir = os.path.expanduser("~/.cache/github-to-rednote") self.cache_dir = Path(cache_dir) self.cache_dir.mkdir(parents=True, exist_ok=True) self.ttl = timedelta(hours=ttl_hours) ``` Authenticated API responses are written using the process's ambient umask: ```python def set(self, endpoint: str, data: Dict): """Cache response data.""" cache_path = self._get_cache_path(endpoint) try: with open(cache_path, 'w', encoding='utf-8') as f: json.dump({ 'cached_at': datetime.now().isoformat(), 'data': data }, f, ensure_ascii=False) except OSError: pass # Ignore cache write errors ``` The cached data may originate from authenticated requests: ```python self.token = token or os.environ.get("GITHUB_TOKEN") ``` ```python headers = { "Authorization": f"Bearer {self.token}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", "User-Agent": "GitHub-to-RedNote/1.0" } ``` ### Technical Analysis The Skill requires a GitHub personal access token and attaches it to requests sent to the fixed `https://api.github.com` origin. If the token authorizes access to private repositories, responses can include private repository metadata, README content, release information, contributor details, and commit summaries. These responses are persisted under `~/.cache/github-to-rednote`. Neither the cache directory nor individual files are explicitly assigned owner-only permissions. Their effective permissions therefore depend on the operating system and process umask. On a multi-user ...[truncated 2176 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the cache directory with owner-only permissions and verify existing permissions: ```python self.cache_dir.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(self.cache_dir, 0o700) ``` 2. Create cache files atomically with mode `0600`. For example, use `os.open()` with explicit flags and permissions, write to a temporary file in the same directory, then replace the destination atomically. 3. Do not cache private repository responses by default. Check the repository's `private` field and require explicit user consent before persisting private data. 4. Namespace cache entries by authenticated identity. Query the authenticated GitHub user once and incorporate a non-sensitive identity identifier into the cache namespace. Do not include raw tokens in filenames or cache files. 5. Consider encrypting private cached data when persistent caching is necessary. 6. Provide clear controls to: - Disable caching. - Set a shorter TTL. - Clear the cache. - Select an isolated cache directory. - Prevent caching of README and commit content. 7. Document that authenticated responses may be persisted locally and identify the default cache location. 8. Avoid silently ignoring cache write failures. Report permission and storage errors so users can determine whether the expected security controls are active. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (33)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill is presented as a formatting/content tool, but the behavior includes direct GitHub API queries, cache management, rate-limit handling, and diagnostic/test execution. When a skill performs operational or test actions not disclosed in its manifest, users may trigger network calls and local state changes they did not intend, which is a meaningful trust and safety issue.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill is presented as a formatting/content tool, but the behavior includes direct GitHub API queries, cache management, rate-limit handling, and diagnostic/test execution. When a skill performs operational or test actions not disclosed in its manifest, users may trigger network calls and local state changes they did not intend, which is a meaningful trust and safety issue.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill is presented as a formatting/content tool, but the behavior includes direct GitHub API queries, cache management, rate-limit handling, and diagnostic/test execution. When a skill performs operational or test actions not disclosed in its manifest, users may trigger network calls and local state changes they did not intend, which is a meaningful trust and safety issue.

Credential Access

High
Category
Privilege Escalation
Content
env:
  GITHUB_TOKEN:
    required: true
    description: GitHub personal access token for API access

agent:
  description: Uses OpenClaw's built-in agent capability for content generation. No external LLM API keys required.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Content
', '💪', '🚀', '✅', '💯', '🌟'],
        'code': ['💻', '⌨️', '🖥️', '🔧', '⚙️', '📝'],
        'star': ['⭐', '🌟', '💫', '✨'],
        'warning': ['⚠️', '❗', '🔔', '💢'],
        'tip': ['💡', '📌', '✏️', '📝', '🎓'],
        'stats': ['📊', '📈', '📉'],
        'link': ['🔗', '🌐', '📎'],
        'user': ['👤', '👥', '🧑‍💻', '👨‍💻', '👩‍💻'],
        'time': ['⏰', '📅', '🕐'],
        'tag': ['🏷️', '📌', '#️⃣']
    }
    
    # Mobile-friendly line width
    MAX_LINE_WIDTH = 32
    
    @staticmethod
    def add_title_emoji(title: str, emoji: str = None, randomize: bool = False) -> str:
        """Add emoji to title."""
        if not emoji:
            if randomize:
                import random
                emoji = random.choice(RedNoteFormatter.EMOJIS['title'])
            else:
                emoji = RedNoteFormatter.EMOJIS['title'][0]
        return f"{emoji} {title
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Env Variable Harvesting

High
Category
Data Exfiltration
Content
# Try using ImageMagick with proper font configuration
    try:
        # Set up font config to handle Chinese
        env = os.environ.copy()
        result = subprocess.run(
            ['convert', '-background', 'none', svg_path, 
             '-resize', f'{COVER_WIDTH}x{COVER_HEIGHT}!',
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
def _build_user_prompt(self, repo_data: Dict, template: str, style: str) -> str:
        """Build user prompt with repository data - Enhanced version with structured sections."""
        
        # Stars display rule: only mention if >= 100
        stars = repo_data.get('stars', 0)
        stars_text = f"{stars:,} stars" if stars >= 100 else ""
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
def _build_user_prompt(self, repo_data: Dict, template: str, style: str) -> str:
        """Build user prompt with repository data - Enhanced version with structured sections."""
        
        # Stars display rule: only mention if >= 100
        stars = repo_data.get('stars', 0)
        stars_text = f"{stars:,} stars" if stars >= 100 else ""
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
def _build_user_prompt(self, repo_data: Dict, template: str, style: str) -> str:
        """Build user prompt with repository data - Enhanced version with structured sections."""
        
        # Stars display rule: only mention if >= 100
        stars = repo_data.get('stars', 0)
        stars_text = f"{stars:,} stars" if stars >= 100 else ""
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
请直接输出文章内容,不要加额外的说明文字。"""
        
        return prompt
    
    def _extract_features_from_readme(self, readme: str) -> list:
        """Extract features section from README."""
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The description says the tool converts GitHub repositories into '小红书风格的技术文章', which implies a Chinese-platform-specific output style by default. Because the README does not indicate that users can choose the output language or opt in to a Chinese locale, this is a natural-language locale policy concern.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises executable capabilities (env, file read/write, network, shell) without declaring an explicit tool scope or permission boundary. That makes the effective privilege set opaque to users and orchestrators, increasing the risk of unintended data access, network activity, or command execution if the skill is invoked in a broader context.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The manifest description says to use the skill when the user wants to generate tech promotion content from GitHub repos, which is a broad natural-language condition rather than a narrowly defined trigger. It does not provide explicit trigger phrases, constraints, or exclusion examples, increasing the chance of unintended invocation for general GitHub-related writing requests.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The overview states that the skill produces articles suitable for Chinese tech community promotion, and the rest of the document reinforces a RedNote-specific Chinese-language format. This appears to impose a locale/language preference without stating that the user can choose another language or explicitly opt into Chinese output.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The manifest declares a language/locale-specific capability, "chinese_technical_writing", as part of the skill's default agent behavior. This can indicate the skill is oriented toward forcing a specific language/locale without an explicit user opt-in or documented choice, which falls under the language/locale policy concerns.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The base prompt instructs the model to act as a creator for Chinese social media and to write articles in Chinese. Repeated later language requirements reinforce a forced locale with no opt-in or alternative, which matches the policy category for language/locale constraints.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
This template specifies 'Language: Chinese (Simplified)' as a hard requirement. The same pattern appears across the prompt set and does not provide a language selection mechanism or clearly scoped compliance justification.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
This template specifies 'Language: Chinese (Simplified)' as a hard requirement. The same pattern appears across the prompt set and does not provide a language selection mechanism or clearly scoped compliance justification.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
This template specifies 'Language: Chinese (Simplified)' as a hard requirement. The same pattern appears across the prompt set and does not provide a language selection mechanism or clearly scoped compliance justification.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
This template specifies 'Language: Chinese (Simplified)' as a hard requirement. The same pattern appears across the prompt set and does not provide a language selection mechanism or clearly scoped compliance justification.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
This template specifies 'Language: Chinese (Simplified)' as a hard requirement. The same pattern appears across the prompt set and does not provide a language selection mechanism or clearly scoped compliance justification.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The guide prescribes default hashtags and examples in Chinese for content output, which imposes a specific language/locale convention. The file does not indicate that this is optional, user-selected, or limited to a justified region-specific workflow.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module description and many output strings are explicitly tailored to RedNote with Chinese text, such as Chinese hashtags and labels, which indicates the formatter enforces a Chinese locale/style by default. The file does not provide any user opt-in, language selection mechanism, or documented justification that this skill is restricted to a Chinese-only regional workflow.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The module docstring states that the script generates RedNote (小红书) articles, and the implementation later builds Chinese-language content strings, implying a fixed output language. This is a natural-language policy concern because the skill forces a specific locale/language without any user opt-in or configurable language selection.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
This code explicitly extracts Chinese README headings and generates Chinese-language selling points such as "社区高度认可" and "文档完善". That imposes a specific output language/locale without any visible user opt-in or configuration, which matches the language/locale policy violation criteria.

Static analysis

No suspicious patterns detected.