T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/publish.sh:248
- Finding
- JavaScript Injection Through Untrusted OG Placeholder Values<![CDATA[ ## Vulnerability Details **File Location**: `scripts/publish.sh`, lines 248–251 **Vulnerability Type**: JavaScript injection through unsafe string interpolation **Risk Level**: High ### Vulnerable Code ```python og_urls = page.evaluate("typeof getOGPlaceholders === 'function' ? getOGPlaceholders() : []") log(f" - OG URLs: {len(og_urls)}") for url in og_urls: page.evaluate(f"prepareOGPlaceholder('{url}')") ``` ### Technical Analysis The `getOGPlaceholders()` helper extracts URL strings from `data-og-placeholder` attributes in the supplied post body. These values therefore originate from the HTML file selected through `--body-file`. Each extracted value is interpolated directly into a JavaScript source string: ```python page.evaluate(f"prepareOGPlaceholder('{url}')") ``` The value is not escaped as a JavaScript string literal. A placeholder containing a single quote followed by JavaScript syntax can terminate the intended argument and inject arbitrary code into the expression evaluated by Playwright. Although normal URLs are expected, `publish.sh` does not establish that the supplied body HTML is trusted or validate placeholder values before evaluating them. The injected expression executes in the context of the authenticated Tistory editor page. This issue differs from the Base64 pre-scan alert. The current Base64 logic in `scripts/tistory-publish.js` converts image bytes into a `Blob` and does not execute the decoded data. ### Attack Path 1. An attacker supplies or influences the HTML file passed through `--body-file`. 2. The HTML contains a crafted `data-og-placeholder` value with a single quote and injected JavaScript. 3. `tinymce.activeEditor.setContent()` inserts the HTML into the editor. 4. `getOGPlaceholders()` reads the attacker-controlled attribute and returns it to the Python process. 5. The value is concatenated into the source passed to `page.evaluate()`. 6. Playwright executes the injected JavaScript in the authenticated Tis ...[truncated 960 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Pass the placeholder as a Playwright evaluation argument instead of constructing JavaScript source: ```python for url in og_urls: page.evaluate("(url) => prepareOGPlaceholder(url)", url) ``` Apply additional input validation before using the value: ```python from urllib.parse import urlparse def validate_og_url(value): parsed = urlparse(value) if parsed.scheme not in ("http", "https") or not parsed.hostname: fail(f"invalid OG URL: {value}") if parsed.username or parsed.password: fail("credentials are not permitted in OG URLs") return value ``` Recommended hardening measures: 1. Treat all body HTML and placeholder attributes as untrusted input. 2. Never interpolate external values into JavaScript source strings. 3. Use Playwright’s structured argument serialization for every dynamic value. 4. Restrict placeholders to absolute `http` or `https` URLs. 5. Optionally enforce an allowlist of domains appropriate for the publishing workflow. 6. Add regression tests containing quotes, backslashes, line separators, and JavaScript-like placeholder values. 7. Reject malformed placeholders before any browser-side evaluation occurs. ]]>
