Skip to content

fix(design): allow cross-origin PNG screenshots from chain - #80

Merged
echobt merged 2 commits into
mainfrom
fix/design-png-direct-cdn
Aug 7, 2026
Merged

fix(design): allow cross-origin PNG screenshots from chain#80
echobt merged 2 commits into
mainfrom
fix/design-png-direct-cdn

Conversation

@echobt

@echobt echobt commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • PNG /v1/view/*/index.png responses now use Cross-Origin-Resource-Policy: cross-origin (gateway + design-http) so joinbase.ai can load screenshots with a direct absolute URL.
  • Non-PNG view paths keep the full HTML lockdown floor (CORP: same-origin, CSP sandbox).
  • Docs updated: public <img src> should hit https://chain.joinbase.ai/challenge/design/v1/view/{runId}/index.png; /gbase-api may remain for JSON only.

Test plan

  • cargo test -p design-sanitize screenshot_headers
  • cargo test -p gateway --lib png_view
  • cargo test -p gateway --test proxy_view_lockdown png_view
  • cargo test -p design-http view_page_serves_screenshots_only
  • cargo run -p xtask -- design-check
  • Deploy gateway before (or with) the frontend PR that points <img src> at chain — otherwise CORP still blocks cross-origin images on current prod.

Summary by CodeRabbit

  • New Features

    • Screenshot previews now support direct cross-origin loading from the gateway.
    • PNG responses use appropriate caching and security headers while preserving cookie protection.
    • Non-PNG viewer responses retain stricter security controls.
  • Bug Fixes

    • Prevented incorrect same-origin policies and stale security headers from blocking screenshot previews.
  • Documentation

    • Clarified screenshot URL requirements and viewer security behavior.

Public gallery <img> tags should hit chain.joinbase.ai directly so Vercel
does not proxy large screenshot bytes. Set CORP cross-origin on PNG view
responses while keeping the HTML lockdown floor for non-PNG paths.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@echobt, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 97b69087-7842-4b23-a4fe-4178fa66a291

📥 Commits

Reviewing files that changed from the base of the PR and between 01b3b8b and 47113a6.

📒 Files selected for processing (1)
  • crates/gateway/src/proxy.rs
📝 Walkthrough

Walkthrough

The PR adds a separate header policy for PNG screenshots. The gateway detects PNG viewer paths, applies cross-origin screenshot headers, removes cookies and stale HTML headers, and documents direct gateway URL usage.

Changes

PNG viewer header policy

Layer / File(s) Summary
Screenshot header contract
crates/design-sanitize/src/lib.rs, crates/design-http/src/api.rs
Adds screenshot_headers() with cross-origin resource policy and basic security headers. PNG API responses use this shared policy.
Gateway PNG routing
crates/gateway/src/proxy.rs, crates/gateway/tests/proxy_view_lockdown.rs
Detects PNG viewer paths, applies PNG headers, removes cookies and stale HTML headers, and tests PNG and HTML behavior.
Viewer URL and policy documentation
docs/DESIGN_CHALLENGE.md, docs/SITE_API.md
Documents PNG header behavior and direct absolute gateway URLs for screenshotUrl. Non-PNG viewer lockdown remains documented.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enabling cross-origin PNG screenshots from the chain.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/design-png-direct-cdn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Rename similar bindings and use Path::extension for case-insensitive
.png detection so -D warnings CI can merge the CORP cross-origin fix.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gateway/src/proxy.rs`:
- Around line 247-258: The apply_view_lockdown function currently selects
screenshot_headers based only on the request path, allowing non-PNG upstream
content to bypass the sandbox CSP. Inspect the upstream response Content-Type
and use screenshot_headers only when it is image/png; otherwise retain
viewer_headers, and add an integration test covering an index.png response with
text/html that verifies the sandbox CSP remains present.

In `@docs/DESIGN_CHALLENGE.md`:
- Around line 231-236: Add the http language identifier to the fenced code block
containing the security response headers, while leaving the block’s contents
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cf848523-3c29-447f-9cb2-294e7c202ec2

📥 Commits

Reviewing files that changed from the base of the PR and between aef598f and 01b3b8b.

📒 Files selected for processing (6)
  • crates/design-http/src/api.rs
  • crates/design-sanitize/src/lib.rs
  • crates/gateway/src/proxy.rs
  • crates/gateway/tests/proxy_view_lockdown.rs
  • docs/DESIGN_CHALLENGE.md
  • docs/SITE_API.md

Comment thread crates/gateway/src/proxy.rs Outdated
Comment on lines +247 to +258
fn apply_view_lockdown(resp: &mut Response, frame_ancestors: &str, rest: &str) {
let headers = resp.headers_mut();
headers.remove(header::SET_COOKIE);
for (k, v) in design_sanitize::viewer_headers(frame_ancestors) {
let floor = if is_view_png_path(rest) {
// Drop HTML-only lockdown if a stale hop set them on a PNG response.
headers.remove(header::CONTENT_SECURITY_POLICY);
headers.remove(HeaderName::from_static("cross-origin-opener-policy"));
headers.remove(HeaderName::from_static("permissions-policy"));
design_sanitize::screenshot_headers()
} else {
design_sanitize::viewer_headers(frame_ancestors)
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep the sandbox header unless the upstream response is declared image/png.

Line 250 classifies the response from the request path only. An upstream can return Content-Type: text/html for an index.png request. This branch then removes CSP and applies the cross-origin screenshot policy, so miner-controlled HTML can execute without the non-PNG sandbox floor.

Check the upstream Content-Type before selecting screenshot_headers(). If the type is not image/png, retain viewer_headers(). Add an integration test where index.png returns text/html and assert that the sandbox CSP remains present.

🧰 Tools
🪛 GitHub Actions: ci / 1_fmt · clippy · test · deny · xtask.txt

[error] 247-247: Clippy similar-names: bindings resp and rest are too similar. Rename one binding or explicitly allow the lint. Failed command: cargo clippy --workspace --all-targets -- -D warnings.

🪛 GitHub Actions: ci / fmt · clippy · test · deny · xtask

[error] 247-247: Clippy similar_names: binding rest is too similar to the existing binding resp. Rename one of the bindings or explicitly allow the lint. Failed under cargo clippy --workspace --all-targets -- -D warnings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/gateway/src/proxy.rs` around lines 247 - 258, The apply_view_lockdown
function currently selects screenshot_headers based only on the request path,
allowing non-PNG upstream content to bypass the sandbox CSP. Inspect the
upstream response Content-Type and use screenshot_headers only when it is
image/png; otherwise retain viewer_headers, and add an integration test covering
an index.png response with text/html that verifies the sandbox CSP remains
present.

Comment thread docs/DESIGN_CHALLENGE.md
Comment on lines +231 to +236
```
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Cross-Origin-Resource-Policy: cross-origin
Cache-Control: private, no-store
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to the fenced block.

Static analysis reports MD040 at Line 231. Mark this block as http so Markdown linting accepts it.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 231-231: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/DESIGN_CHALLENGE.md` around lines 231 - 236, Add the http language
identifier to the fenced code block containing the security response headers,
while leaving the block’s contents unchanged.

Source: Linters/SAST tools

@echobt
echobt merged commit 0f42d63 into main Aug 7, 2026
3 checks passed
@echobt
echobt deleted the fix/design-png-direct-cdn branch August 7, 2026 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant