Back to skill

Security audit

Blog Publisher

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent blog-publishing workflow, but it uses broad Git publishing commands and under-disclosed external image-generation behavior that users should review before installing.

Install only if you are comfortable letting the agent modify and push the dev-blog repository. Before use, replace git add -A with explicit file paths, review the staged diff, avoid force-push or use force-with-lease only with confirmation, and require a clean working tree. Treat image prompts and Telegram-transferred files as shared with third parties, avoid secrets or sensitive drafts in prompts, strip image metadata when needed, and use restricted provider API keys.

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

Warning
Location
SKILL.md:219
Finding
Overbroad Git staging and destructive preview branch force-push<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 219–223 **Vulnerability Type**: Overbroad file staging and destructive Git operation **Risk Level**: Medium ### Vulnerable Code ```bash cd ~/projects/dev-blog git checkout -B preview git add -A git commit -m "Add blog post: {title}" git push origin preview --force ``` ### Technical Analysis The publishing workflow stages the entire repository with `git add -A`. This includes all modified, deleted, and untracked files that are not excluded by Git ignore rules, rather than only the generated blog posts and associated image assets. Consequently, unrelated drafts, configuration files, local artifacts, or sensitive files can be included in the deployment commit. The subsequent unconditional `git push origin preview --force` replaces the remote preview branch tip without checking whether the branch contains concurrent changes. These operations exceed the minimum privileges and change scope needed to publish a specific blog post. The workflow only needs to stage the generated `.mdoc` files and approved image assets, and it normally does not need to rewrite remote branch history. ### Attack Path 1. An attacker, another local process, or an unrelated development task places a sensitive or malicious file in the `dev-blog` working tree, or modifies an existing tracked file. 2. The Skill begins the documented publishing workflow without first requiring a clean working tree. 3. `git add -A` stages the unrelated change along with the intended blog files. 4. The commit permanently includes all staged content. 5. `git push origin preview --force` publishes the commit and overwrites the current remote preview branch tip. 6. The unrelated content may become accessible through the preview deployment or repository history, while concurrent remote work may be discarded. This path requires write access to the local repository or the ability to influence its working tree. It does not independently grant ...[truncated 812 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require a clean working tree before making generated changes: ```bash test -z "$(git status --porcelain)" || { echo "Refusing to publish from a non-clean working tree." exit 1 } ``` 2. Stage only the files created or modified for the current post: ```bash git add -- \ "src/content/blog/ko/${slug}.mdoc" \ "src/content/blog/en/${slug}.mdoc" \ "src/assets/${approved_image_name}.webp" ``` 3. Review the exact staged file list and patch before committing: ```bash git diff --cached --name-status git diff --cached ``` 4. Reject staged paths outside an explicit allowlist such as `src/content/blog/` and `src/assets/`. 5. Replace the unconditional force-push with a normal push: ```bash git push origin preview ``` 6. If branch replacement is genuinely required, obtain explicit user approval and use `--force-with-lease` rather than `--force`: ```bash git push origin preview --force-with-lease ``` 7. Confirm that no secrets or local configuration files are staged before creating the commit. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/generate-image.py:65
Finding
Google API credential embedded in the request URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate-image.py`, lines 65–73 **Vulnerability Type**: Credential exposure through a URL query parameter **Risk Level**: Low ### Vulnerable Code ```python if style: prompt = f"{prompt} {style}" url = f"{GOOGLE_BASE_URL}/{model}:generateContent?key={api_key}" payload = json.dumps({ "contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"responseModalities": ["IMAGE", "TEXT"]} }).encode() req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"}) ``` ### Technical Analysis The Google API key is placed directly in the URL query string as `?key=...`. The request is sent over HTTPS to the declared Google Generative Language API, and use of a credential is necessary to invoke the remote image-generation service. No malicious recipient or unrelated credential exfiltration was identified. However, credentials in query strings are more likely than header-based credentials to be captured by URL logs, HTTP proxy telemetry, monitoring products, exception reports, or debugging tools. TLS protects the URL while it is in transit, but it does not prevent local clients, intermediaries that terminate TLS, or server-side infrastructure from recording the complete request URL. The image prompt is also intentionally transmitted to the selected external provider. Users should therefore avoid placing confidential information in prompts and should be informed of this external disclosure. ### Attack Path 1. The user configures `GOOGLE_API_KEY` and invokes the script with the Google provider. 2. The script constructs a URL containing the complete credential in the `key` query parameter. 3. A local debugging facility, enterprise HTTPS proxy, observability platform, or provider-side URL logger records the request URL. 4. A person or compromised service with access to those logs retrieves the API key. 5. The exposed key is use ...[truncated 828 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Where supported by the Google API, transmit the key in the dedicated authentication header instead of the URL: ```python url = f"{GOOGLE_BASE_URL}/{model}:generateContent" req = urllib.request.Request( url, data=payload, headers={ "Content-Type": "application/json", "x-goog-api-key": api_key, }, ) ``` 2. Ensure errors and diagnostics never include authentication headers or complete request URLs containing credentials. 3. Apply API-level and source restrictions to the Google key and configure the minimum necessary quota. 4. Rotate the key if it may already have appeared in proxy, application, or monitoring logs. 5. Configure logging and observability systems to redact common credential parameters such as `key`, `api_key`, and `token`. 6. Document that image prompts are sent to the selected external provider and instruct users not to include secrets or confidential source material in prompts. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (14)

Tainted flow: 'req' from os.environ.get (line 110, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})

    try:
        with urllib.request.urlopen(req, timeout=180) as resp:
            data = json.loads(resp.read())
    except Exception as e:
        print(f"ERROR: Google API call failed: {e}", file=sys.stderr)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 110, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})

    try:
        with urllib.request.urlopen(req, timeout=180) as resp:
            data = json.loads(resp.read())
    except Exception as e:
        print(f"ERROR: Google API call failed: {e}", file=sys.stderr)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
설명은 블로그 발행 파이프라인과 이미지 프롬프트 제공/후처리를 말하지만, 실제 코드 조각은 블로그 발행이나 문서 변환, 배포 승인 흐름을 다루지 않는다. 대신 Google AI Studio 또는 OpenRouter에 직접 요청을 보내 AI 이미지를 생성하는 기능이 핵심이다. 특히 '사용자가 직접 생성한 이미지 후처리'와 달리 이 코드는 이미지를 생성한다는 점이 materially different primary purpose에 해당한다. 또한 외부 API 키를 읽고 원격 서비스에 요청하는 능력은 설명에 드러나지 않는다. 따라서 설명과 실제 동작은 불일치한다.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
git checkout -B preview
git add -A
git commit -m "Add blog post: {title}"
git push origin preview --force
```

확인 링크:
Confidence
96% confidence
Finding
Using `git push origin preview --force` allows the skill to overwrite remote branch history unconditionally. In an automated workflow, this can destroy collaborators' work, erase audit history on the preview branch, and make recovery difficult if the agent operates on stale or incorrect state.

Credential Access

High
Category
Privilege Escalation
Content
import urllib.request
from pathlib import Path

# Load .env from skill directory
env_path = Path(__file__).parent.parent / ".env"
if env_path.exists():
    with open(env_path) as f:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
from pathlib import Path

# Load .env from skill directory
env_path = Path(__file__).parent.parent / ".env"
if env_path.exists():
    with open(env_path) as f:
        for line in f:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill describes operations that require filesystem, environment, git, and network access, but it does not declare an explicit tool scope such as permissions or allowed-tools. This increases the chance an agent will run with broader capabilities than intended, making unintended repository modification, credential exposure, or outbound actions harder to constrain and audit.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill instructs users to transmit generated images through Telegram without warning about third-party handling, retention, or the possibility that images may contain sensitive information or metadata. This can lead to privacy leakage, especially if screenshots, drafts, or embedded EXIF/location data are shared through an external messaging platform.

Context-Inappropriate Capability

Medium
Confidence
83% confidence
Finding
The manifest describes publishing blog content, converting Markdown, and handling user-generated images, but this script proactively loads secrets from a local .env file into the process environment. While networked image generation itself is documented, custom credential harvesting from local files is a broader capability than the manifest states and is not obviously required by the declared skill purpose.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script transmits user-supplied prompts to external AI services without any explicit consent prompt, privacy notice, or warning that prompt contents leave the local environment. In a blog-publishing workflow, prompts may contain unpublished drafts, proprietary ideas, or personal data, so silent transmission increases confidentiality and compliance risk.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The instructions say to write a detailed English prompt for image generation, which imposes a language choice in the workflow. Because the file does not offer user opt-in or explain that English is required for a specific tool limitation, this is a natural-language locale policy issue.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The markdown states that text inside images should preferably be in English, which is a locale/language preference expressed as a rule. This can conflict with the policy against forcing a specific language unless users are given a choice or the constraint is clearly justified.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The script's primary natural-language documentation and several comments are written in Korean, while the tool itself does not offer any language selection or document a locale-specific constraint. This can violate language/locale policy when a skill implicitly forces a specific language without user opt-in.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The module usage comment states that Google AI Studio is the default provider, but the actual default is taken from IMAGE_PROVIDER with a fallback of "openrouter". This is an active contradiction between the script documentation and runtime behavior, which can mislead operators about which external service receives prompts by default.

Static analysis

No suspicious patterns detected.