fix(web): use the separate-origin preview for in-app HTML rendering (#59)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3f868ac385
commit
6b8b631e37
8 files changed
+340
-49
No files matched your search
@@ -151,7 +151,8 @@ export function requestAuthority(requestUrl: string, hostHeader: string | undefi
|
||||
/**
|
||||
* Where previews for this request should be served from: the configured origin when
|
||||
* PENGUIN_PREVIEW_ORIGIN is set, otherwise the loopback counterpart of the host the
|
||||
* caller is using. Null when neither applies — the caller degrades to the same-origin
|
||||
* caller is using. Null when neither applies — or when the configured origin's host is
|
||||
* the request's own host (no boundary) — and the caller degrades to the same-origin
|
||||
* sandbox rather than silently serving unisolated content.
|
||||
*
|
||||
* The port comes from **this server's own binding**, never from the request. Those differ
|
||||
@@ -169,13 +170,23 @@ export function resolvePreviewTarget(
|
||||
configuredOrigin: string | null,
|
||||
serverBind: { host: string; port: number },
|
||||
): { origin: string; host: string } | null {
|
||||
const requestHost = hostOnly(requestAuthority(requestUrl, hostHeader)).toLowerCase();
|
||||
if (configuredOrigin) {
|
||||
const url = new URL(configuredOrigin);
|
||||
// A configured origin that lands on the very host this request is using is no
|
||||
// boundary at all: "isolated" previews would be same-origin with the App, and the
|
||||
// allow-same-origin iframe the UI mounts for them would hand Agent HTML the App's
|
||||
// origin. Such a misconfiguration degrades to null — previewIsolated reports false
|
||||
// and the redirect falls back to the same-origin sandbox — instead of claiming an
|
||||
// isolation that does not exist. Host-only comparison on purpose: cookies ignore
|
||||
// ports, so a port difference would not separate anything either.
|
||||
if (url.hostname.toLowerCase() === requestHost) return null;
|
||||
return { origin: url.origin, host: url.hostname };
|
||||
}
|
||||
const raw = requestAuthority(requestUrl, hostHeader);
|
||||
if (raw === "") return null;
|
||||
const counterpart = loopbackCounterpart(hostOnly(raw));
|
||||
if (requestHost === "") return null;
|
||||
// The loopback counterpart differs from the request host by construction, so the
|
||||
// same-host guard above is only needed for the configured branch.
|
||||
const counterpart = loopbackCounterpart(requestHost);
|
||||
if (!counterpart) return null;
|
||||
if (!loopbackHostRoles(serverBind.host)) return null;
|
||||
|
||||
|
||||
@@ -128,6 +128,39 @@ describe("preview origin derivation", () => {
|
||||
resolvePreviewTarget("http://192.168.1.5:7364/x", "192.168.1.5:7364", null, bind),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("refuses a configured origin that matches the App request's host", () => {
|
||||
// PENGUIN_PREVIEW_ORIGIN pointing back at the host the App is being used on is no
|
||||
// boundary: "isolated" previews would be same-origin with the App (and the UI would
|
||||
// mount an allow-same-origin iframe onto them). Must degrade to null so
|
||||
// previewIsolated reports false and the safe same-origin sandbox engages.
|
||||
expect(
|
||||
resolvePreviewTarget(
|
||||
"https://app.example.com/x",
|
||||
"app.example.com",
|
||||
"https://app.example.com",
|
||||
bind,
|
||||
),
|
||||
).toBeNull();
|
||||
// Port and case differences don't rescue it: cookies ignore ports, hosts ignore case.
|
||||
expect(
|
||||
resolvePreviewTarget(
|
||||
"https://app.example.com/x",
|
||||
"app.example.com:8443",
|
||||
"https://APP.example.com:9443",
|
||||
bind,
|
||||
),
|
||||
).toBeNull();
|
||||
// A genuinely different host on the same registrable domain is still accepted.
|
||||
expect(
|
||||
resolvePreviewTarget(
|
||||
"https://app.example.com/x",
|
||||
"app.example.com",
|
||||
"https://preview.app.example.com",
|
||||
bind,
|
||||
),
|
||||
).toEqual({ origin: "https://preview.app.example.com", host: "preview.app.example.com" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("preview route", () => {
|
||||
|
||||
Reference in new issue
Block a user