Back to skill

Security audit

celebrate

Security checks for vulnerabilities and agentic risk

Overview

The skill’s purpose is coherent, but its renderer has under-disclosed unsafe config paths that can execute raw HTML/JavaScript and write files outside the intended output folder.

Review before installing. Use this only with configs and screenshots you control, avoid the undocumented headline_html and banner_headline_html fields, do not render celebration JSON from untrusted sources, and choose output/name/slug values that are simple filenames. Sensitive screenshots should be cropped locally and checked before rendering because the browser-based renderer can expose embedded proof data if hostile HTML is present.

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

Error
Location
scripts/render_cards.py:341
Finding
Arbitrary HTML and JavaScript Injection Through Raw Headline Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/render_cards.py:341` and `scripts/render_cards.py:398-400` **Vulnerability Type**: Untrusted HTML injection in generated browser content **Risk Level**: High ### Vulnerable Code ```python def title(self, fs): return '<h1 style="font-size:%dpx">%s</h1>' % ( fs, self.c.get("headline_html") or esc(self.c.get("headline", "")) ) ``` ```python head = self.c.get("banner_headline_html") or ( esc(self.c.get("headline", "")) + ' <em>&middot; ' + esc(self.c.get("banner_suffix", "")) + '</em>' if self.c.get("banner_suffix") else esc(self.c.get("headline", "")) ) ``` The generated page is subsequently opened in headless Chrome: ```python cmd = [ chrome, "--headless", "--disable-gpu", "--hide-scrollbars", "--force-device-scale-factor=%d" % args.scale, "--virtual-time-budget=3000", "--window-size=%d,%d" % (f["w"], f["h"]), "--screenshot=" + png_path, "file:///" + html_path.replace("\\", "/"), ] proc = subprocess.run(cmd, capture_output=True, text=True, timeout=180) ``` ### Technical Analysis Most configuration text is processed by `esc()`, but the `headline_html` and `banner_headline_html` configuration properties bypass escaping and are inserted verbatim into an HTML document. Because the generated document is loaded by a full headless browser, these properties can contain active markup such as `<script>` elements or event handlers. The Chrome invocation does not disable JavaScript or network connectivity. Consequently, rendering an untrusted configuration can execute attacker-controlled JavaScript in the browser context. This is not equivalent to direct native command execution, but injected JavaScript can access the generated document, including the Base64-embedded proof image, and can attempt outbound requests or browser-based interactions with services reachable from the host. ### Attack Path 1. An attacker supplies or influences a ce ...[truncated 1709 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `headline_html` and `banner_headline_html` if raw HTML is not essential. 2. Represent formatting through structured configuration rather than executable markup, such as separate plain-text and emphasis fields. 3. If limited HTML must be supported, sanitize it with a strict allowlist that permits only required formatting elements such as `<em>` and rejects: - `<script>`, `<iframe>`, `<object>`, and `<embed>` - Event-handler attributes such as `onload` and `onerror` - URLs and unsafe attributes - SVG and MathML active content 4. Escape all text nodes and attribute values according to their HTML context. 5. Consider disabling JavaScript for the screenshot process if the layout does not require it. The current fit pass uses JavaScript, so replacing it with a trusted static mechanism or isolated preprocessing would be necessary first. 6. Run the renderer in a sandbox with restricted network access when processing configurations from untrusted sources. 7. Add regression tests using script tags, event handlers, malformed HTML, SVG payloads, and encoded injection variants. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render_cards.py:447
Finding
Arbitrary Output File Placement Through Unvalidated Configuration Slug<![CDATA[ ## Vulnerability Details **File Location**: `scripts/render_cards.py:447` and `scripts/render_cards.py:464-466` **Vulnerability Type**: Path traversal and unintended file overwrite **Risk Level**: Medium ### Vulnerable Code ```python card = Card(cfg, base) slug = cfg.get("slug") or os.path.splitext(os.path.basename(cfg_path))[0] ``` ```python for suffix, cta_text in variants: name = "%s-%s%s" % (slug, key, suffix) html_path = os.path.join(tmp, name + ".html") png_path = os.path.join(out, name + ".png") with open(html_path, "w", encoding="utf-8") as fh: fh.write(card.render_html(f["layout"], f["w"], f["h"], cta_text)) ``` ### Technical Analysis The `slug` value comes directly from the JSON configuration and is incorporated into filesystem paths without validation. It can contain: - Parent-directory traversal components such as `../` - Platform-specific path separators - Absolute path prefixes `os.path.join()` does not enforce containment within `tmp` or `out`. In particular, an absolute second operand causes the preceding base directory to be discarded. Traversal components can similarly resolve outside the intended directory. The script writes generated HTML itself and instructs Chrome to write generated PNG files to the resulting paths. Existing files at compatible destination paths may be overwritten when the process has permission and the required parent directories exist. ### Attack Path 1. An attacker supplies a celebration configuration with a malicious `slug`, such as: ```json { "headline": "Celebration", "slug": "../../outside/target", "formats": ["x"] } ``` 2. The renderer constructs a name such as `../../outside/target-x`. 3. `html_path` and `png_path` resolve outside the intended `.html` and output directories. 4. The renderer writes generated HTML to the escaped location. 5. Headless Chrome is given the escaped PNG destination and may create or overwrite that file. 6. The oper ...[truncated 820 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict `slug` to a filename-safe pattern, for example: ```python import re if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", slug): die("slug contains unsupported characters") ``` 2. Explicitly reject absolute paths, path separators, `.` and `..` path components. 3. Resolve each output path and verify containment before writing: ```python def contained_path(root, filename): root = os.path.realpath(root) target = os.path.realpath(os.path.join(root, filename)) if os.path.commonpath([root, target]) != root: die("generated path escapes output directory") return target ``` 4. Use exclusive creation or require explicit overwrite confirmation where existing outputs could be replaced. 5. Apply equivalent validation on Windows and POSIX systems, accounting for both slash styles, drive letters, and UNC paths. 6. Add tests for `../`, absolute paths, Windows drive paths, UNC paths, nested separators, and symbolic-link boundary cases. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/prepare_proof.py:40
Finding
Proof Derivative Path Traversal Through Unvalidated Name Argument<![CDATA[ ## Vulnerability Details **File Location**: `scripts/prepare_proof.py:40` and `scripts/prepare_proof.py:81-86` **Vulnerability Type**: Path traversal and unintended file overwrite **Risk Level**: Low ### Vulnerable Code ```python ap.add_argument("--name", default="proof", help="basename for the derivative") ``` ```python cropped = os.path.join(args.out, args.name + "-raw.png") im.save(cropped) lit = ImageEnhance.Brightness(im).enhance(args.brightness) lit = ImageEnhance.Contrast(lit).enhance(args.contrast) bright = os.path.join(args.out, args.name + ".png") lit.save(bright) ``` ### Technical Analysis Although the option is described as a basename, the script does not enforce basename semantics. The `--name` argument may contain path separators, parent-directory components, or an absolute path. The two resulting paths are passed directly to Pillow's `Image.save()`. As a result, a caller able to control command-line arguments can direct the cropped and enhanced PNG derivatives outside the selected `--out` directory. This issue does not introduce a new privilege boundary when a trusted user manually chooses the arguments. It becomes security-relevant when the script is invoked automatically with attacker-controlled or agent-generated parameters. ### Attack Path 1. An attacker influences the `--name` argument used to invoke the script. 2. The attacker supplies a traversal value, for example: ```bash python scripts/prepare_proof.py shot.png \ --out proof \ --name ../../outside/target ``` 3. The script constructs paths equivalent to: - `proof/../../outside/target-raw.png` - `proof/../../outside/target.png` 4. Pillow writes the generated images outside the intended proof directory. 5. Existing PNG files at those locations may be overwritten if the current user has write permission. ### Impact Assessment Successful exploitation permits creation or replacement of two PNG files outside the configured output directory unde ...[truncated 446 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `--name` as a simple filename stem and reject separators and traversal components: ```python import re if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", args.name): die("--name must be a filename-safe basename") ``` 2. Reject absolute names explicitly with `os.path.isabs(args.name)`. 3. Resolve both destination paths and verify that `os.path.commonpath()` keeps them beneath the resolved output directory. 4. Consider refusing to overwrite existing derivatives unless an explicit `--force` option is supplied. 5. Add cross-platform tests for POSIX traversal, Windows separators, drive-letter paths, UNC paths, and symbolic-link escapes. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description promises a broader asset-production workflow: turning a win into a set of designed, on-brand assets for many channels and sizes. The supplied code does something much narrower: it preserves the original screenshot, optionally crops it, and creates a brightened/contrast-enhanced derivative image. That is related to preparing proof imagery, but it does not implement multi-size export, branding, layout composition, channel-specific formatting, or verification logic. So the code represents only a small preprocessing step, not the declared end-to-end skill behavior.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill clearly instructs use of shell commands, file reads/writes, environment-variable overrides, and local scripts, but declares no explicit tool scope or permissions boundary. That increases the chance an agent will execute higher-risk capabilities than a user expects, especially when handling screenshots and producing files on disk.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The trigger list includes broad phrases like "show off," "celebrate this," and "announcement image," which can match ordinary conversation and cause unintended activation. In a skill that invokes shell, file, and possible network behavior, accidental triggering can lead to unnecessary processing of user files or external requests without clear intent.

External Transmission

Medium
Category
Data Exfiltration
Content
|---|---|
| GitHub stars, forks, age | `gh api repos/OWNER/REPO --jq '{stars:.stargazers_count,forks:.forks_count,created:.created_at}'` |
| GitHub releases, contributors | `gh api repos/OWNER/REPO/releases --jq 'length'`, `.../contributors?per_page=100` |
| npm downloads | `curl -s https://api.npmjs.org/downloads/point/last-month/PACKAGE` |
| PyPI downloads | pypistats, or the BigQuery public dataset |
| Crate downloads | `curl -s https://crates.io/api/v1/crates/NAME` |
| Docker pulls | `curl -s https://hub.docker.com/v2/repositories/OWNER/NAME/` |
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
|---|---|
| GitHub stars, forks, age | `gh api repos/OWNER/REPO --jq '{stars:.stargazers_count,forks:.forks_count,created:.created_at}'` |
| GitHub releases, contributors | `gh api repos/OWNER/REPO/releases --jq 'length'`, `.../contributors?per_page=100` |
| npm downloads | `curl -s https://api.npmjs.org/downloads/point/last-month/PACKAGE` |
| PyPI downloads | pypistats, or the BigQuery public dataset |
| Crate downloads | `curl -s https://crates.io/api/v1/crates/NAME` |
| Docker pulls | `curl -s https://hub.docker.com/v2/repositories/OWNER/NAME/` |
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"file:///" + html_path.replace("\\", "/"),
            ]
            try:
                proc = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
            except subprocess.TimeoutExpired:
                failures.append(name + " (browser timed out)")
                print("FAIL  " + name)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The README states that the skill verifies numbers against a live source and uses browser-based capture, which implies outbound network access and interaction with potentially sensitive screenshots or metadata, but it does not clearly warn users about that behavior. In an agent-skill context, undocumented network access can surprise users, cause unintended disclosure of private leaderboard/dashboard data, or violate expectations in restricted environments.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
return self.page(w, h, body)

    def render_html(self, layout, w, h, cta):
        return getattr(self, layout)(w, h, cta)


def main():
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.