T09 · Insecure Skill Coding Practices
- Location
scripts/render-image.js:130- Finding
Unrestricted Network Access While Rendering Arbitrary HTML
- Content
View full analysis
Vulnerability Details
File Location:
scripts/render-image.js, lines 130–131
Vulnerability Type: Arbitrary HTML execution with unrestricted browser networking
Risk Level: MediumVulnerable Code
js await page.goto("file://" + path.resolve(inputPath), { waitUntil: "networkidle0" }); await page.screenshot({ path: outputPath, fullPage: opts.full });The script also makes an inaccurate security claim at
scripts/render-image.js:19:js * No credentials, tokens, or network access are required.A similar claim appears in
render-image-setup.md:5:md No credentials, tokens, or network access are required.Technical Analysis
The renderer accepts a caller-selected HTML file and opens it in a fully script-capable Chromium page. It does not disable JavaScript, intercept network requests, enforce an offline content policy, or restrict resource destinations.
Although the bundled templates contain no scripts or external resources, the renderer itself does not enforce that property. A crafted input document can therefore execute JavaScript and load remote scripts, images, stylesheets, fonts, frames, or other network resources. Chromium's sandbox reduces the likelihood of direct host compromise, but it does not prevent outbound network communication.
This conflicts with the documented self-contained, network-free rendering model. The risk is especially relevant if attacker-controlled post content is inserted into generated HTML without correct context-sensitive escaping.
Attack Path
- An attacker supplies content that is incorporated into an HTML card, or convinces the user or agent to render an attacker-controlled HTML file.
- The resulting document contains an active payload, such as a script, remote image, stylesheet, iframe, or other external resource.
- The user or agent invokes
scripts/render-image.jswith that document as the input. - Puppeteer opens the document with JavaScript and browser networking e ...[truncated 1044 chars]
- Remediation
View remediation
Remediation Suggestions
- Enforce an offline request policy. Enable Puppeteer request interception before navigating and reject every request except explicitly permitted local schemes:
js await page.setRequestInterception(true); page.on("request", (request) => { const url = new URL(request.url()); if (url.protocol === "file:" || url.protocol === "data:") { request.continue(); } else { request.abort("blockedbyclient"); } });- Disable JavaScript by default if the templates do not require it:
js await page.setJavaScriptEnabled(false);If JavaScript is required in the future, expose it only through an explicit opt-in option and retain strict network blocking.
-
Validate the input path. Resolve the path, confirm that it is a regular file, reject unsupported extensions, and optionally restrict inputs to an approved workspace directory.
-
Escape user-controlled content. Use context-sensitive escaping before placing user material into HTML text, attributes, URLs, CSS, or script contexts. Prefer DOM text assignment or a template system with automatic escaping over string concatenation.
-
Add a restrictive Content Security Policy to generated templates, for example:
html <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data: file:; style-src 'unsafe-inline'; font-src data: file:; script-src 'none'" />Request interception should remain the primary control because the renderer accepts arbitrary HTML that may omit or weaken a document-level policy.
-
Correct the documentation. Do not claim that network access is absent until the renderer technically enforces that guarantee. Clearly state whether untrusted HTML is supported.
-
Add automated security tests. Render a test document that attempts HTTP, HTTPS, iframe, image, stylesheet, and script requests. The test should fail unless every external request is blocked.
