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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.7.5",
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("x-forwarded-proto", reqUrl.protocol.replace(":", ""));
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
  /**
@@ -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 { Handle, RequestEvent } from "./hooks.ts";
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
- for (const [k, v] of Object.entries(SECURITY_HEADERS)) headers.set(k, v);
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