From 47b21a6ecaa8b7f72469046b26e671fb1738f657 Mon Sep 17 00:00:00 2001 From: Mitchell-Johnson Date: Sat, 19 Sep 2026 09:11:35 -0700 Subject: [PATCH] Hub proxy: don't relay a node's policy headers on top of the hub's own Access-Control-Allow-Origin arrived twice on /api/nodes//sensors, which a browser on another origin refuses. Also: demo lists say when the token hides them. Co-Authored-By: Claude Fable 5.1 --- examples/external-demo/index.html | 2 +- internal/nodes/nodes.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/external-demo/index.html b/examples/external-demo/index.html index d3e7910..6d88b58 100644 --- a/examples/external-demo/index.html +++ b/examples/external-demo/index.html @@ -113,7 +113,7 @@ const W = { list: (map, label, pattern, opts = {}) => { const rows = Object.values(map || {}).filter((s) => !opts.filter || opts.filter(s)).sort((a, b) => a.id.localeCompare(b.id)) return h('div', { class: 'tile wide' }, h('div', { class: 'label' }, `${label} ยท ${rows.length}`), - rows.length ? h('div', { class: 'rows' }, rows.map((s) => { const f = frac(s); return h('div', { class: 'row' }, h('span', { class: 'k', title: s.id }, opts.name ? opts.name(s) : s.name), typeof s.value === 'boolean' ? h('span', { class: 'pill ' + (s.value ? 'ok' : 'bad') }, s.value ? 'up' : 'down') : h('span', { class: 'n' }, fmt(s)), f === null ? null : h('div', { class: 'meter' }, h('i', { class: sev(s), style: `width:${f * 100}%` }))) })) : h('div', { class: 'mut' }, 'nothing matches on this machine'), + rows.length ? h('div', { class: 'rows' }, rows.map((s) => { const f = frac(s); return h('div', { class: 'row' }, h('span', { class: 'k', title: s.id }, opts.name ? opts.name(s) : s.name), typeof s.value === 'boolean' ? h('span', { class: 'pill ' + (s.value ? 'ok' : 'bad') }, s.value ? 'up' : 'down') : h('span', { class: 'n' }, fmt(s)), f === null ? null : h('div', { class: 'meter' }, h('i', { class: sev(s), style: `width:${f * 100}%` }))) })) : h('div', { class: 'mut' }, 'nothing here that this token may see'), h('div', { class: 'id mono' }, pattern)) }, } diff --git a/internal/nodes/nodes.go b/internal/nodes/nodes.go index bb5056b..2d2c4cd 100644 --- a/internal/nodes/nodes.go +++ b/internal/nodes/nodes.go @@ -442,6 +442,15 @@ func (m *Manager) Proxy(name string) (http.Handler, bool) { }, Transport: transportFor(n), FlushInterval: -1, // stream task output / log tails immediately + // The node answers with its own policy headers and this hub adds the same ones to + // everything it serves; relayed twice, Access-Control-Allow-Origin is invalid and a + // browser on another origin refuses the response. The hub's copy is the one that counts. + ModifyResponse: func(resp *http.Response) error { + for _, h := range []string{"Access-Control-Allow-Origin", "X-Frame-Options", "Content-Security-Policy", "X-Content-Type-Options", "Referrer-Policy"} { + resp.Header.Del(h) + } + return nil + }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadGateway)