Summary
visual_screenshot has two bugs surfaced during an MCP gateway full-surface smoke test on mcp.stackbilt.dev:
- URL mode is completely broken — passing a
url parameter returns a validation error as if image_base64 were required.
- FK violations leak D1 internal stack traces to the client, including Cloudflare internal paths.
Repro 1 — URL mode
```json
// Input
{ "url": "https://imgforge.stackbilt.dev", "page_id": "imgforge-landing" }
// Response
{ "error": "page_id and image_base64 are required" }
```
The schema description says url is accepted and mutually exclusive with image_base64. The error message contradicts both the schema and the request payload (which does include page_id). Either:
- The URL → headless-Chrome path never wires up, and the validator falls through to the base64 branch, OR
- The validator checks for
image_base64 presence unconditionally before branching on url.
Either way, users trying the documented URL workflow hit a 400 that doesn't match their input.
Repro 2 — Stack trace leak
```json
// Input — page_id NOT in the visual_pages table
{ "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAA...", "page_id": "smoke-test-1px" }
// Response (verbatim)
{
"error": "D1_ERROR: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT (extended: SQLITE_CONSTRAINT_FOREIGNKEY)",
"stack": [
"Error: D1_ERROR: FOREIGN KEY constraint failed: ...",
" at D1DatabaseSessionAlwaysPrimary._sendOrThrow (cloudflare-internal:d1-api:139:19)",
" at async cloudflare-internal:d1-api:353:41"
]
}
```
Response exposes:
- Internal D1 implementation (
cloudflare-internal:d1-api:139:19)
- Line numbers of Cloudflare internals
- The fact that
page_id is a FK column in SQLite
Proposed fix
URL mode:
Restructure validation:
```ts
if (!url && !image_base64) {
return err("one of url or image_base64 required");
}
if (url) {
// headless Chrome path
} else {
// base64 upload path
}
```
Error hygiene:
Wrap all D1 calls in a boundary handler. Map D1 error codes to structured client errors:
```ts
catch (e) {
if (isD1FKError(e)) {
return err("page_id not in monitored pages list", "INVALID_PAGE_ID", 400);
}
logger.error({ err: e }); // full trace goes to logs, not client
return err("internal error", "INTERNAL", 500);
}
```
No stack[] array in any client-facing response. Ever.
Severity
Medium-High.
- URL mode broken → feature is documented but non-functional for every user
- Stack trace leak → security issue, reveals runtime internals. Low-priority CVE-class but not zero.
Context
Found via full-surface MCP smoke test. Also see companion issue for visual_analyze URL mode — likely cascading from the same broken URL code path.
Summary
visual_screenshothas two bugs surfaced during an MCP gateway full-surface smoke test onmcp.stackbilt.dev:urlparameter returns a validation error as ifimage_base64were required.Repro 1 — URL mode
```json
// Input
{ "url": "https://imgforge.stackbilt.dev", "page_id": "imgforge-landing" }
// Response
{ "error": "page_id and image_base64 are required" }
```
The schema description says
urlis accepted and mutually exclusive withimage_base64. The error message contradicts both the schema and the request payload (which does includepage_id). Either:image_base64presence unconditionally before branching onurl.Either way, users trying the documented URL workflow hit a 400 that doesn't match their input.
Repro 2 — Stack trace leak
```json
// Input — page_id NOT in the visual_pages table
{ "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAA...", "page_id": "smoke-test-1px" }
// Response (verbatim)
{
"error": "D1_ERROR: FOREIGN KEY constraint failed: SQLITE_CONSTRAINT (extended: SQLITE_CONSTRAINT_FOREIGNKEY)",
"stack": [
"Error: D1_ERROR: FOREIGN KEY constraint failed: ...",
" at D1DatabaseSessionAlwaysPrimary._sendOrThrow (cloudflare-internal:d1-api:139:19)",
" at async cloudflare-internal:d1-api:353:41"
]
}
```
Response exposes:
cloudflare-internal:d1-api:139:19)page_idis a FK column in SQLiteProposed fix
URL mode:
Restructure validation:
```ts
if (!url && !image_base64) {
return err("one of url or image_base64 required");
}
if (url) {
// headless Chrome path
} else {
// base64 upload path
}
```
Error hygiene:
Wrap all D1 calls in a boundary handler. Map D1 error codes to structured client errors:
```ts
catch (e) {
if (isD1FKError(e)) {
return err("page_id not in monitored pages list", "INVALID_PAGE_ID", 400);
}
logger.error({ err: e }); // full trace goes to logs, not client
return err("internal error", "INTERNAL", 500);
}
```
No
stack[]array in any client-facing response. Ever.Severity
Medium-High.
Context
Found via full-surface MCP smoke test. Also see companion issue for
visual_analyzeURL mode — likely cascading from the same broken URL code path.