From b86e139bdd24e94c83852e86734409de10062a83 Mon Sep 17 00:00:00 2001 From: Derick Phan Date: Wed, 8 Apr 2026 10:11:57 -0700 Subject: [PATCH] Privacy hardening: degoogle, security headers, referrer protection - Replace Google Fonts with system font stacks across all themes - Add Referrer-Policy, X-Content-Type-Options, X-Frame-Options, CSP headers - Add rel="noreferrer noopener" on all outbound links - Add no-referrer and dns-prefetch-control meta tags to all themes - Clean tracking params on outbound links from trusted/remote sources - Remove Google domains from CSP whitelists Co-Authored-By: Claude Opus 4.6 --- gateway.py | 8 + handlers.py | 19 +- templates.py | 4 +- themes/junimo.html | 25 +-- themes/kodama.html | 481 ++++++++++++++++++++++---------------------- themes/kodama2.html | 23 ++- 6 files changed, 285 insertions(+), 275 deletions(-) diff --git a/gateway.py b/gateway.py index a13816f..ffafc6a 100644 --- a/gateway.py +++ b/gateway.py @@ -123,6 +123,14 @@ class GatewayHandler(BaseHTTPRequestHandler): self.send_response(resp["status"]) self.send_header("Content-Type", resp.get("content_type", "text/html; charset=utf-8")) + self.send_header("Referrer-Policy", "no-referrer") + self.send_header("X-Content-Type-Options", "nosniff") + self.send_header("X-Frame-Options", "DENY") + self.send_header("Content-Security-Policy", + "default-src 'self'; " + "style-src 'self' 'unsafe-inline'; " + "script-src 'self' 'unsafe-inline'; " + "img-src 'self' data:") for k, v in resp.get("headers", {}).items(): self.send_header(k, v) self.end_headers() diff --git a/handlers.py b/handlers.py index ae7f484..6a03142 100644 --- a/handlers.py +++ b/handlers.py @@ -245,7 +245,7 @@ def handle_search(query): snip_html = f'
{esc(r["summary"])}' if r["summary"] else "" result_html += ( f'
' - f'{esc(r["title"])}
' + f'{esc(r["title"])}
' f'{esc(r["url"])}' f'{snip_html}' f'{note_html}{tags_html}' @@ -276,7 +276,7 @@ def handle_search(query): items = "" for l in trusted: items += ( - f'
  • {esc(l["label"])} ' + f'
  • {esc(l["label"])} ' f'— from {esc(l["source_title"])}
  • ' ) trusted_html = ( @@ -311,8 +311,8 @@ def handle_search(query): for r in items: note_html = f' — {esc(r["note"])}' if r["note"] else "" source_items += ( - f'
  • {esc(r["title"])}' - f'{note_html} ({esc(r["url"])})
  • ' + f'
  • {esc(r["title"])}' + f'{note_html} ({esc(clean_url(r["url"]))})
  • ' ) remote_html += ( f'
    ' @@ -473,7 +473,7 @@ def handle_add_manual_submit(body): # Log error but don't fail the whole operation print(f"Error generating embeddings: {e}") - return handle_add_form(f'Added manually: {esc(manual_title)}') + return handle_add_form(f'Added manually: {esc(manual_title)}') finally: return_db(db) @@ -500,7 +500,7 @@ def handle_pages(query=None): tags_html = f' {tag_links}' items += ( f'
  • {esc(r["title"])}{note_html}{tags_html} ' - f'({esc(r["url"])}) ' + f'({esc(r["url"])}) ' f'edit ' f'remove
  • ' ) @@ -700,7 +700,7 @@ def handle_style_form(msg=""): f"Default: reticulum.derickphan.com:4242
    " f'' f'
    ' - f'

    discover more nodes


    ' + f'

    discover more nodes


    ' f"

    search

    " f"

    ai

    " f'