Back to skill

Security audit

Blog Composer

Security checks for vulnerabilities and agentic risk

Overview

This is a real blog editor, but it exposes powerful local file, web, AI, and publishing actions without enough containment or disclosure.

Review before installing or running. The skill should bind only to localhost, require authentication for the web API, validate post filenames against a strict allowlist, sanitize rendered Markdown/metadata, disclose AI providers and prompt data sharing, remove hardcoded secret paths, and add clear confirmation before delete or publish actions.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/server.py:219
Finding

Unauthenticated Path Traversal Enables Arbitrary File Read, Write, and Deletion

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
index.html:438
Finding

Stored Cross-Site Scripting Through Unsanitized Markdown and Post Metadata

Content
View full analysis
Start typing to see preview...'; text = text.replace(/^---[\s\S]*?---\n/, ''); text = text.replace(/^# (.+)$/gm, '

$1

'); text = text.replace(/^## (.+)$/gm, '

$1

'); text = text.replace(/^### (.+)$/gm, '

$3

'); text = text.replace(/\*\*(.+?)\*\*/g, '$1'); text = text.replace(/\*(.+?)\*/g, '$1'); text = text.replace(/`([^`]+)`/g, '$1'); text = text.replace(/```(\w*)\n([\s\S]*?)```/g, '
text
$2
'); text = text.replace(/^> (.+)$/gm, '
$1
'); text = text.replace(/^---$/gm, '
'); text = text.replace(/^- (.+)$/gm, '
  • $1
  • '); text = text.replace(/(
  • .*<\/li>\n?)+/g, '
      $&
    '); text = text.replace(/\|(.+)\|/g, (m) => { const cells = m.split('|').filter(c => c.trim()); if (cells.some(c => /-+/.test(c))) return ''; const isHeader = !m.includes('
      '); const tag = isHeader ? 'th' : 'td'; return '' + cells.map(c => `<${tag}>${c.trim()}`).join('') + ''; }); text = text.replace(/(.*<\/tr>\n?)+/g, '$&
      '); text = text.replace(/\n\n/g, '

      '); text = '

      ' + text + '

      '; text = text.replace(/

      <(h[123]|blockquote|ul|pre|hr|table)/g, '<$1'); text = text.replace(/<\/(h[123]|blockquote|ul|pre|hr|table)><\/p>/g, ''); return text; } ``` The returned string is inserted into `innerHTML`: ```javascript editor.value = data.content; preview.innerHTML = renderMarkdown(data.content); ``` Post metadata is also interpolated directly into HTML and inline JavaScript: ```j ...[truncated 2702 chars]

  • Remediation
    View remediation

    T05 · Unauthorized Access and Privilege Escalation

    Warning
    Location
    src/gui.py:783
    Finding

    Undocumented Access to an External OpenClaw API Credential

    Content
    View full analysis
    Remediation
    View remediation

    T09 · Insecure Skill Coding Practices

    Warning
    Location
    src/gui.py:742
    Finding

    Undisclosed Transmission of User Topics and Prompts to External Providers

    Content
    View full analysis
    Remediation
    View remediation
    Vulnerability Patterns
    • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
    • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
    • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
    • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
    • Behavioral ASTexec() Call, eval() Call, Dynamic Import
    Findings (42)

    Tp4

    High
    Category
    MCP Tool Poisoning
    Confidence
    97% confidence
    Finding

    The skill metadata presents a limited Jekyll authoring UI, but the reported behavior includes AI generation, outbound web requests, and subprocess execution of tools like ollama and git. That mismatch is dangerous because users and policy systems may grant file/http permissions expecting only content management, while hidden secondary capabilities can expand data exposure, command execution, and content manipulation risk.

    Content

    No source excerpt is available for this finding.

    Hidden Instructions

    High
    Category
    Prompt Injection
    Confidence
    70% confidence
    Finding

    Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

    Content

    Scanner excerpt · index.html (reported line 189)May include surrounding context.

    html
    <div class="tab" data-tab="publish">Publish</div>
    </div>
    
    <!-- WRITE PANEL -->
    <div class="panel active" id="panel-write">
      <div class="write-layout">
        <div class="editor-pane">
    

    Hidden Instructions

    High
    Category
    Prompt Injection
    Confidence
    70% confidence
    Finding

    Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

    Content

    Scanner excerpt · index.html (reported line 303)May include surrounding context.

    html
    </div>
    </div>
    
    <!-- PREVIEW PANEL -->
    <div class="panel" id="panel-preview">
      <div class="preview-content" id="preview-full" style="max-width: 720px; margin: 0 auto;">Start typing to see full preview...</div>
    </div>
    

    Lp1

    High
    Category
    MCP Least Privilege
    Confidence
    93% confidence
    Finding

    The skill declares file and HTTP permissions but also executes git subprocesses, effectively introducing shell/process-execution capability beyond the manifest. Undeclared execution capability weakens policy enforcement and can surprise operators who rely on the permission model to bound risk.

    Content

    No source excerpt is available for this finding.

    Context-Inappropriate Capability

    High
    Category
    Not specified by scanner
    Confidence
    98% confidence
    Finding

    Reading a local secret file to access an external AI API is outside the narrow expectation of a blog editor and creates a clear sensitive-data handling issue. Hard-coded secret paths also leak environmental assumptions and make accidental credential exposure or misuse more likely.

    Content

    No source excerpt is available for this finding.

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    97% confidence
    Finding

    This generation path sends prompts and user-provided topic/research context to an external AI provider while authenticating with a locally read API key. In a blog-authoring tool, that means locally authored content and associated metadata may leave the workstation, and the secret-handling model is weak due to hard-coded on-disk secret access.

    Content

    Scanner excerpt · src/gui.py (reported line 812)May include surrounding context.

    python
    def do_http():
                        try:
                            with urllib.request.urlopen(req, timeout=120) as res:
                                result = json.loads(res.read())
                                content = result["content"][0]["text"].strip()
                                gen_state["text"] = content
    

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    80% confidence
    Finding

    File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

    Content

    Scanner excerpt · src/gui.py (reported line 902)May include surrounding context.

    python
    f"&srsearch={urllib.parse.quote(topic)}&srlimit=6&format=json"
                       f"&prop=extracts&exintro=1&explaintext=1")
                req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Sol Blog Composer)"})
                with urllib.request.urlopen(req, timeout=10) as res:
                    data = json.loads(res.read())
                    for r in data.get("query", {}).get("search", []):
                        snippet = re.sub(r'<[^>]+>', '', r.get("snippet", ""))
    

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    80% confidence
    Finding

    File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

    Content

    Scanner excerpt · src/gui.py (reported line 925)May include surrounding context.

    python
    f"&srsearch={urllib.parse.quote(topic)}&srlimit=6&format=json"
                       f"&prop=extracts&exintro=1&explaintext=1")
                req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Sol Blog Composer)"})
                with urllib.request.urlopen(req, timeout=10) as res:
                    data = json.loads(res.read())
                    for r in data.get("query", {}).get("search", []):
                        snippet = re.sub(r'<[^>]+>', '', r.get("snippet", ""))
    

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    80% confidence
    Finding

    File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

    Content

    Scanner excerpt · src/gui.py (reported line 1824)May include surrounding context.

    python
    f"&srsearch={urllib.parse.quote(topic)}&srlimit=6&format=json"
                       f"&prop=extracts&exintro=1&explaintext=1")
                req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Sol Blog Composer)"})
                with urllib.request.urlopen(req, timeout=10) as res:
                    data = json.loads(res.read())
                    for r in data.get("query", {}).get("search", []):
                        snippet = re.sub(r'<[^>]+>', '', r.get("snippet", ""))
    

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    80% confidence
    Finding

    File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

    Content

    Scanner excerpt · src/gui.py (reported line 1847)May include surrounding context.

    python
    f"&srsearch={urllib.parse.quote(topic)}&srlimit=6&format=json"
                       f"&prop=extracts&exintro=1&explaintext=1")
                req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Sol Blog Composer)"})
                with urllib.request.urlopen(req, timeout=10) as res:
                    data = json.loads(res.read())
                    for r in data.get("query", {}).get("search", []):
                        snippet = re.sub(r'<[^>]+>', '', r.get("snippet", ""))
    

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    97% confidence
    Finding

    The code reads a local API key from disk and includes it in an outbound request header to an external service. While using an API key to authenticate is expected, this still constitutes sensitive secret handling and external transmission; compromise of the process, logs, debugging hooks, or a redirected endpoint could expose the credential.

    Content

    Scanner excerpt · src/gui.py (reported line 1026)May include surrounding context.

    python
    data=data, headers=headers, method="POST"
            )
            try:
                with urllib.request.urlopen(req, timeout=120) as res:
                    result = json.loads(res.read())
                    # MiniMax returns content as a list of blocks (thinking + text)
                    text_parts = []
    

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    97% confidence
    Finding

    This duplicated generation path has the same risk profile: local secret retrieval plus outbound AI request. Duplicate code increases the chance of inconsistent fixes and makes accidental exposure or silent transmission harder to audit.

    Content

    Scanner excerpt · src/gui.py (reported line 1734)May include surrounding context.

    python
    def do_http():
                        try:
                            with urllib.request.urlopen(req, timeout=120) as res:
                                result = json.loads(res.read())
                                content = result["content"][0]["text"].strip()
                                gen_state["text"] = content
    

    Tainted flow: 'req' from open (line 1727, file read) → urllib.request.urlopen (network output)

    High
    Category
    Data Flow
    Confidence
    97% confidence
    Finding

    This second MiniMax request path also reads a local secret from disk and transmits it as an authentication header to an external service. Hard-coded local secret access combined with network egress expands the blast radius if the skill or host is misconfigured or monitored.

    Content

    Scanner excerpt · src/gui.py (reported line 1948)May include surrounding context.

    python
    data=data, headers=headers, method="POST"
            )
            try:
                with urllib.request.urlopen(req, timeout=120) as res:
                    result = json.loads(res.read())
                    # MiniMax returns content as a list of blocks (thinking + text)
                    text_parts = []
    

    Scope Creep

    High
    Category
    Not specified by scanner
    Confidence
    96% confidence
    Finding

    The code uses subprocess execution while the manifest only declares file.read, file.write, and http.request. This means the implementation has more effective capability than users and reviewers are told, undermining permission transparency and making abuse or unintended side effects harder to assess.

    Content

    No source excerpt is available for this finding.

    Context-Inappropriate Capability

    High
    Category
    Not specified by scanner
    Confidence
    93% confidence
    Finding

    The server executes local AI model binaries even though the stated purpose is a blog authoring UI. Running undeclared local processes materially increases capability: it can consume local resources, interact with system-installed tooling, and process sensitive prompts or repository content in ways users may not expect from a simple editor.

    Content

    No source excerpt is available for this finding.

    Scope Creep

    High
    Category
    Not specified by scanner
    Confidence
    97% confidence
    Finding

    Invoking external Ollama processes falls outside the declared permission scope and grants the skill undeclared execution power. In context, an HTTP endpoint can trigger these local processes remotely, amplifying the danger through resource exhaustion, unintended local data handling, and reduced user visibility.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    87% confidence
    Finding

    This markdown file advertises "One-click publish to GitHub Pages," "Save draft," and "delete post" capabilities, which can modify or remove user content. The description does not include any caution, confirmation expectation, backup note, or warning about affecting files in the target blog repository.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    92% confidence
    Finding

    The README explicitly says posts are saved directly to ~/Projects/thesolai.github.io/_posts/, which is a user-data-affecting file write location. Because this is a markdown file, the skill description should warn that using the tool will create or modify files in that repository and could overwrite existing content if used incorrectly.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    93% confidence
    Finding

    The skill description and notes describe capabilities that modify files and publish content but do not warn users that invoking the skill may change local blog files or transition drafts to published state. Missing warnings reduce informed consent and can lead to accidental content loss, premature publication, or unintended repository changes, especially in an authoring workflow with write permissions.

    Content

    No source excerpt is available for this finding.

    Vague Triggers

    Medium
    Category
    Not specified by scanner
    Confidence
    91% confidence
    Finding

    The trigger conditions are broad enough to match ordinary writing, editing, or content-management requests, which can cause the skill to activate in contexts broader than a user's intended Jekyll blog workflow. Overbroad activation increases the chance of unintended file modifications, publishing actions, or use of any hidden capabilities in situations where a narrower tool should have been selected.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    93% confidence
    Finding

    The Generate feature collects a user-supplied topic plus generation preferences and sends them to a backend endpoint, with an option to 'Research first' that implies external web access. Because the UI provides no clear disclosure about where this content is sent, how it is used, or whether third parties may receive it, users may unintentionally transmit sensitive draft ideas or proprietary information.

    Content

    No source excerpt is available for this finding.

    Context-Inappropriate Capability

    Medium
    Category
    Not specified by scanner
    Confidence
    87% confidence
    Finding

    The manifest says the skill manages posts, drafts, tags, and publishing workflow for static sites, but the implementation performs local subprocess execution via git commands. Spawning subprocesses is a materially broader capability than file editing or HTTP requests and is not disclosed by the description, even though publishing itself is in scope.

    Content

    No source excerpt is available for this finding.

    subprocess module call

    Medium
    Category
    Dangerous Code Execution
    Confidence
    70% confidence
    Finding

    subprocess module calls execute external commands. Without careful input validation, this enables command injection.

    Content

    Scanner excerpt · src/gui.py (reported line 73)May include surrounding context.

    python
    return content[m.end():] if m else content
    
    def git_run(*args, cwd=BLOG_DIR):
        r = subprocess.run(["git"] + list(args), cwd=cwd, capture_output=True, text=True, timeout=30)
        return r.returncode == 0, r.stdout.strip(), r.stderr.strip()
    
    def markdown_to_html(text):
    

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    94% confidence
    Finding

    Publishing immediately performs git add/commit/push to a remote repository without a final confirmation step summarizing what will be sent. That can lead to accidental disclosure or irreversible publication of drafts, secrets, or unwanted edits with a single click.

    Content

    No source excerpt is available for this finding.

    Description-Behavior Mismatch

    Medium
    Category
    Not specified by scanner
    Confidence
    91% confidence
    Finding

    The manifest describes a blog authoring UI, but the code also performs internet research and sends prompts to an external AI service. This scope expansion matters because users and administrators may not expect content egress or third-party processing from the declared functionality.

    Content

    No source excerpt is available for this finding.

    Static analysis

    No suspicious patterns detected.