Skip to content

visual_screenshot: URL mode non-functional + D1 stack trace leaks on error #35

@stackbilt-admin

Description

@stackbilt-admin

Summary

visual_screenshot has two bugs surfaced during an MCP gateway full-surface smoke test on mcp.stackbilt.dev:

  1. URL mode is completely broken — passing a url parameter returns a validation error as if image_base64 were required.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions