garrytan / garrytan/gstack

office-hours wireframe render silently fails because browse blocks file:// URLs

Open
#771 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
133k
Forks
19.9k
Avg merge
18h 46m
Merged PRs (30d)
26

Description

## Summary

The office-hours skill generates a wireframe HTML file at `/tmp/gstack-sketch-*.html`, then tries to render it with:

```bash
$B goto "file://$SKETCH_FILE"
$B screenshot /tmp/gstack-sketch.png
```

This silently fails because the browse binary blocks `file://` URLs (added in #17 to prevent SSRF/local resource access). The skill hits the fallback path ("skip the render step") and moves on without telling the user what happened.

## Root Cause

`validateNavigationUrl()` in the browse binary rejects any URL with a `file:` scheme. This is correct for untrusted external content, but the office-hours skill generates the HTML itself — the file is trusted local content.

The skill's fallback message says:
> "Visual sketch requires the browse binary. Run the setup script to enable it."

This is misleading — the browse binary IS set up and working. The issue is the URL scheme, not the binary.

## Reproduction

1. Run `/office-hours` on any project with a UI component
2. Let it reach the "Visual Sketch" section where it generates wireframe HTML
3. Observe: `$B goto "file:///tmp/gstack-sketch-*.html"` exits with code 1 and the error `Blocked: scheme "file:" is not allowed`
4. The skill silently skips the render, screenshot, and "present and iterate" steps

## Workaround

Serve the file over HTTP before navigating:

```bash
python3 -m http.server 18923 --directory /tmp --bind 127.0.0.1 &
$B goto "http://127.0.0.1:18923/gstack-sketch-1234.html"
$B screenshot /tmp/gstack-sketch.png
kill %1
```

This works perfectly — the browse binary renders and screenshots the wireframe without issues.

## Suggested Fix

In the office-hours skill template (`office-hours/SKILL.md.tmpl`), replace the direct `file://` navigation with a temporary HTTP server pattern:

```bash
SKETCH_PORT=$(shuf -i 18000-19000 -n 1)
python3 -m http.server "$SKETCH_PORT" --directory "$(dirname "$SKETCH_FILE")" --bind 127.0.0.1 &
_HTTP_PID=$!
sleep 1
$B goto "http://127.0.0.1:$SKETCH_PORT/$(basename "$SKETCH_FILE")"
$B screenshot /tmp/gstack-sketch.png
kill $_HTTP_PID 2>/dev/null
```

Alternatively, the browse binary could allowlist `file://` URLs that are under `/tmp/gstack-*` since the skill itself generates them and they're trusted.

## Related

- #17 — original SSRF fix that added `file://` blocking
- #340 — user-facing symptoms of this bug (wireframes never rendered, "present and iterate" step skipped)

## Environment

- macOS (aarch64-darwin)
- gstack browse binary built and working (verified with `http://` URLs)
- bun, Google Chrome available via Nix

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.