From 8eb27ed9b4ac1103d4836e1f5e41b72dca76e6b9 Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 31 Jul 2026 05:38:48 -0400 Subject: [PATCH] fix(security-headers): allow iframe embedding when ARCHY_EMBEDDED=1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit secureHeaders() defaulted to X-Frame-Options: SAMEORIGIN, added as part of the NIP-98/JWT auth hardening. This unconditionally blocked the Archipelago node dashboard's iframe (a different origin by port) — 1.1.0 never sent this header at all, so this was a hard regression for the platform's normal embedded-app UX. Fix: X-Frame-Options is now conditional on ARCHY_EMBEDDED=1, an env var the archy manifest sets for the node-installed instance (first-party, trusted embedding on the same host). Standalone/public-arena instances keep the default SAMEORIGIN clickjacking protection unchanged. Verified: with ARCHY_EMBEDDED=1 no X-Frame-Options header is sent; without it, X-Frame-Options: SAMEORIGIN is still sent as before. Co-Authored-By: Claude Fable 5 --- server/src/app.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/app.ts b/server/src/app.ts index 73cf701..35a0d9c 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -53,7 +53,16 @@ app.use('*', async (c, next) => { }) // Security headers: X-Frame-Options, X-Content-Type-Options, HSTS, Referrer-Policy, etc. +// ARCHY_EMBEDDED=1 means this instance is running as an app inside the +// Archipelago node dashboard's iframe (a first-party, trusted embedding +// context on the same host, different port — never a third-party site). +// X-Frame-Options: SAMEORIGIN (the secureHeaders default) blocks that framing +// outright, since the dashboard and this app are different origins by port. +// Standalone/public-arena instances (ARCHY_EMBEDDED unset) keep the default +// clickjacking protection. +const isEmbedded = process.env.ARCHY_EMBEDDED === '1' app.use('*', secureHeaders({ + xFrameOptions: isEmbedded ? false : true, contentSecurityPolicy: process.env.NODE_ENV === 'production' ? { defaultSrc: ["'self'"], scriptSrc: ["'self'", 'blob:', "'wasm-unsafe-eval'"],