bosia 0.7.5 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/dev.ts +10 -2
- package/src/core/hooks.ts +13 -0
- package/src/core/server.ts +24 -2
- package/src/lib/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosia",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
|
|
6
6
|
"keywords": [
|
package/src/core/dev.ts
CHANGED
|
@@ -346,9 +346,17 @@ const devServer = Bun.serve({
|
|
|
346
346
|
target.hostname = "127.0.0.1";
|
|
347
347
|
target.port = String(APP_PORT);
|
|
348
348
|
|
|
349
|
+
// Preserve an X-Forwarded-* set by an OUTER proxy (e.g. a multi-tenant host
|
|
350
|
+
// fronting `bun run dev` behind TLS). Overwriting it with this dev proxy's
|
|
351
|
+
// own loopback host/scheme would strip the real public origin, so the app's
|
|
352
|
+
// redirects and `event.url` would point at localhost. Fall back to this
|
|
353
|
+
// proxy's request only when the outer hop didn't set them.
|
|
349
354
|
const forwardedHeaders = new Headers(req.headers);
|
|
350
|
-
forwardedHeaders.set("x-forwarded-host", reqUrl.host);
|
|
351
|
-
forwardedHeaders.set(
|
|
355
|
+
forwardedHeaders.set("x-forwarded-host", req.headers.get("x-forwarded-host") ?? reqUrl.host);
|
|
356
|
+
forwardedHeaders.set(
|
|
357
|
+
"x-forwarded-proto",
|
|
358
|
+
req.headers.get("x-forwarded-proto") ?? reqUrl.protocol.replace(":", ""),
|
|
359
|
+
);
|
|
352
360
|
// Force inner app to respond uncompressed. Bun's `fetch()` auto-decodes
|
|
353
361
|
// gzip/br bodies but leaves the original `Content-Encoding` header on
|
|
354
362
|
// the Response, so passing it through made Safari throw -1015 ("cannot
|
package/src/core/hooks.ts
CHANGED
|
@@ -113,6 +113,19 @@ export type Metadata = {
|
|
|
113
113
|
|
|
114
114
|
type MaybePromise<T> = T | Promise<T>;
|
|
115
115
|
|
|
116
|
+
// ─── Frame-Guard Opt-Out ──────────────────────────────────
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Internal response header a `handle` can set to opt a single response out of
|
|
120
|
+
* the global `X-Frame-Options: SAMEORIGIN` guard. Use when a trusted proxy hub
|
|
121
|
+
* serves another origin's content that must be embeddable (e.g. an app preview
|
|
122
|
+
* iframe): the proxy strips the upstream `X-Frame-Options`, but the framework
|
|
123
|
+
* would otherwise re-add its own. The header is stripped before the response
|
|
124
|
+
* leaves the process, so it never reaches the client. Other security headers
|
|
125
|
+
* (`X-Content-Type-Options`, `Referrer-Policy`) are unaffected.
|
|
126
|
+
*/
|
|
127
|
+
export const NO_FRAME_GUARD_HEADER = "x-bosia-no-frame-guard";
|
|
128
|
+
|
|
116
129
|
// ─── Middleware Composition ────────────────────────────────
|
|
117
130
|
|
|
118
131
|
/**
|
package/src/core/server.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type { RouteManifest } from "./types.ts";
|
|
|
12
12
|
// Pre-compile route patterns into RegExp at startup (shared by renderer.ts via module reference)
|
|
13
13
|
compileRoutes(apiRoutes);
|
|
14
14
|
compileRoutes(serverRoutes);
|
|
15
|
-
import type
|
|
15
|
+
import { NO_FRAME_GUARD_HEADER, type Handle, type RequestEvent } from "./hooks.ts";
|
|
16
16
|
import { HttpError, Redirect, ActionFailure } from "./errors.ts";
|
|
17
17
|
import { CookieJar } from "./cookies.ts";
|
|
18
18
|
import { safePath } from "./safePath.ts";
|
|
@@ -781,6 +781,20 @@ if (_xfoDisabled) {
|
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
784
|
+
// Behind a trusted proxy the inbound `Host`/scheme is the proxy's inner hop
|
|
785
|
+
// (e.g. `localhost:PORT` over plain HTTP), so `url` built from `request.url`
|
|
786
|
+
// misreports the public origin. Rebuild it from `X-Forwarded-Host`/`-Proto`
|
|
787
|
+
// so `event.url` — and every absolute redirect, canonical URL, and
|
|
788
|
+
// `url.origin` the app derives — points at the public-facing origin instead
|
|
789
|
+
// of localhost. Gated on TRUST_PROXY since these headers are client-spoofable
|
|
790
|
+
// when no proxy strips them.
|
|
791
|
+
if (TRUST_PROXY) {
|
|
792
|
+
const fwdHost = request.headers.get("x-forwarded-host");
|
|
793
|
+
if (fwdHost) url.host = fwdHost;
|
|
794
|
+
const fwdProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
795
|
+
if (fwdProto) url.protocol = `${fwdProto}:`;
|
|
796
|
+
}
|
|
797
|
+
|
|
784
798
|
// Reject new non-health requests during shutdown
|
|
785
799
|
if (shuttingDown && url.pathname !== "/_health") {
|
|
786
800
|
return new Response("Service Unavailable", {
|
|
@@ -828,7 +842,15 @@ async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
|
828
842
|
const response = userHandle ? await userHandle({ event, resolve }) : await resolve(event);
|
|
829
843
|
|
|
830
844
|
const headers = new Headers(response.headers);
|
|
831
|
-
|
|
845
|
+
// A handle can mark a response (e.g. a proxied embeddable preview) to opt
|
|
846
|
+
// out of the frame guard. Strip the internal marker so it never ships, and
|
|
847
|
+
// skip only X-Frame-Options for that response — other security headers stay.
|
|
848
|
+
const skipFrameGuard = headers.has(NO_FRAME_GUARD_HEADER);
|
|
849
|
+
headers.delete(NO_FRAME_GUARD_HEADER);
|
|
850
|
+
for (const [k, v] of Object.entries(SECURITY_HEADERS)) {
|
|
851
|
+
if (skipFrameGuard && k === "X-Frame-Options") continue;
|
|
852
|
+
headers.set(k, v);
|
|
853
|
+
}
|
|
832
854
|
const cspHeader = buildCspHeader(nonce);
|
|
833
855
|
if (cspHeader) headers.set("Content-Security-Policy", cspHeader);
|
|
834
856
|
// Apply CORS headers for allowed origins. `Vary: Origin` is set whenever
|
package/src/lib/index.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// import type { RequestEvent, LoadEvent, Handle, Cookies } from "bosia"
|
|
7
7
|
|
|
8
8
|
export { cn, getServerTime } from "./utils.ts";
|
|
9
|
-
export { sequence } from "../core/hooks.ts";
|
|
9
|
+
export { sequence, NO_FRAME_GUARD_HEADER } from "../core/hooks.ts";
|
|
10
10
|
export { error, redirect, fail } from "../core/errors.ts";
|
|
11
11
|
// `invalidate` / `invalidateAll` (server response-cache eviction) live in
|
|
12
12
|
// "bosia/server" — they touch server-process state and pulling them into
|