From a4a12bfe2a055d904632875b7f01a979b4305643 Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Wed, 30 Sep 2026 06:42:36 +0000 Subject: [PATCH] load skopec.dev live in its window via an allowlisted header-stripping proxy (/api/frame) --- README.md | 8 +- agents.md | 18 ++++- src/app/api/frame/route.ts | 108 ++++++++++++++++++++++++++ src/app/components/WindowsDesktop.tsx | 13 ++-- src/app/data.ts | 12 +++ 5 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 src/app/api/frame/route.ts diff --git a/README.md b/README.md index 1acc166..80bfcee 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,10 @@ heatmap**, a gym SBD panel and links to friends' sites. A spinner covers the frame while the site boots. Discord sends `DENY` and keeps a link card instead. - **Friend sites in windows** — clicking a friend's desktop icon opens their site in a - window. `bukovinafilip.com` and `vlcak.me` send no framing headers, so they load live; - `skopec.dev` sends `X-Frame-Options: SAMEORIGIN`, so the window frames the bundled mirror - at `/skopec.html` and the `www` button opens the real site. + window. `bukovinafilip.com` and `vlcak.me` send no framing headers, so they load directly; + `skopec.dev` sends `X-Frame-Options: SAMEORIGIN`, so it loads through `/api/frame`, a + header-stripping proxy that serves the live page from our origin. The `www` button always + opens the real site. - **Minecraft-style SMP window** — the SMP shortcut opens a join menu modelled on Minecraft's multiplayer screen, with live status, player count, MOTD and version pulled from mcstatus.io through the site's own ping API. @@ -156,6 +157,7 @@ they contend for `:3000` and `.next/dev/lock`. | --- | --- | --- | | `/api/heatmap` | `GET` | Proxies the user's contribution heatmap from `git.dvorakmortonxd.dev`. | | `/api/activities` | `GET` | Fetches the GitDXD activity feed and keeps only the user's own events. | +| `/api/frame?url=…` | `GET` | Header-stripping proxy for a friend's site that refuses framing (`skopec.dev`). **Allow-listed hosts only**; fetches the HTML server-side (so the site's `X-Frame-Options` never reaches the browser), injects a `` for relative assets and a click shim for in-site links. Rejects other hosts, non-http(s) schemes and credentials, and never follows redirects. | | `/api/ping?url=…` | `GET` | Health-checks an **allow-listed** service (the site's own `*.dvorakmortonxd.dev` hosts and the friends' sites). Handles `minecraft://host:port` via mcstatus.io; treats any non-5xx response as "up". Other hosts and schemes are rejected and redirects are never followed, so it can't be used to probe the LAN (SSRF guard). | ## 📁 Project Structure diff --git a/agents.md b/agents.md index eb293ce..d947705 100644 --- a/agents.md +++ b/agents.md @@ -84,6 +84,7 @@ src/app/ heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap activities/route.ts → proxy for the user's GitDXD activity feed ping/route.ts → service health check (HTTP + minecraft:// via mcstatus.io); host allowlist + frame/route.ts → header-stripping proxy so an unframeable friend site loads in a window; host allowlist components/ ThemeRoot.tsx → picks which shell to show (the router of the UI) TerminalLayout.tsx → "terminal" theme (Gruvbox) @@ -299,10 +300,16 @@ list reordering, not free placement. Don't confuse the two. is the `embed` flag; `friend.url` is what the frame loads and `friend.realUrl` is what the www button opens — `createWebsiteApp`'s `embedUrl` option lets the two differ. `bukovinafilip.com` and `vlcak.me` send no framing headers, so they - frame the live site; `skopec.dev` sends `X-Frame-Options: SAMEORIGIN` on every - path, so `friends[0].url` points at the bundled `/public/skopec.html` mirror - and only the button is live. Making a blocking site load *live* would need a - header-stripping proxy (allowlisted) — not implemented. + frame the live site. `skopec.dev` sends `X-Frame-Options: SAMEORIGIN` on every + path, so `friend.proxy` routes it through `/api/frame`. +- `/api/frame` is a **host-allowlisted** header-stripping proxy for sites that + refuse framing. It fetches the HTML server-side (so the upstream + `X-Frame-Options` never reaches the browser), injects `` + so relative assets still resolve to the real site, and injects a click shim so + same-host navigation is proxied too. It only proxies `ALLOWED_HOSTS` + (`skopec.dev`), never follows redirects, rejects credentials and non-http(s), + and returns HTML only — it is public, so keep it that narrow. The framed page + executes under our origin, so only allow-list hosts you trust. - **Embedded iframes swallow the pointer.** While dragging/resizing a window, the moment the cursor passes over a service `