cmc/dotfiles

Using GNU Stow to manage my dotfiles.

clone: git clone https://gitbay.org/cmc/dotfiles.git

263f281b30ac0f7daaf654e2e9abee71f9d80709

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-08-03T17:03:54Z

fix security headers for new vhosts
 linux/nginx/etc/nginx/conf.d/i.conf                 | 13 +++++++++++++
 linux/nginx/etc/nginx/conf.d/i.conf.bak-i-onion     |  9 +++++++++
 linux/nginx/etc/nginx/conf.d/krz.sh.conf            |  1 +
 .../etc/nginx/custom.d/http/content_type_maps.conf  | 21 +++++++++++++++++++--
 .../security/content-security-policy-krz.conf       | 10 ++++++++++
 5 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/linux/nginx/etc/nginx/conf.d/i.conf b/linux/nginx/etc/nginx/conf.d/i.conf
index 23cef0d..ab0d429 100644
--- a/linux/nginx/etc/nginx/conf.d/i.conf
+++ b/linux/nginx/etc/nginx/conf.d/i.conf
@@ -1,9 +1,22 @@
 server {
 	listen 127.0.0.1:10048;
 	server_name i.krz.sh;
+	add_header Onion-Location "http://j42zbkbxrdx4mvkyutt7jiwpucgcmrdvaelwcwmgwn3mzus2v6x3toad.onion$request_uri" always;
 	include custom.d/basic.conf;
 	include custom.d/security/content-security-policy.conf;
 	root /var/www/i/;
 	autoindex on;
 	location / { try_files $uri $uri/ =404; }
 }
+
+# Onion counterpart of i.krz.sh. Reached via tor -> 127.0.0.1:10049, so it
+# never transits Cloudflare; nginx is the only thing setting headers here.
+server {
+    listen 127.0.0.1:10049;
+    server_name j42zbkbxrdx4mvkyutt7jiwpucgcmrdvaelwcwmgwn3mzus2v6x3toad.onion;
+    include custom.d/basic.conf;
+    include custom.d/security/content-security-policy.conf;
+    root /var/www/i/;
+    autoindex on;
+    location / { try_files $uri $uri/ =404; }
+}
diff --git a/linux/nginx/etc/nginx/conf.d/i.conf.bak-i-onion b/linux/nginx/etc/nginx/conf.d/i.conf.bak-i-onion
new file mode 100644
index 0000000..23cef0d
--- /dev/null
+++ b/linux/nginx/etc/nginx/conf.d/i.conf.bak-i-onion
@@ -0,0 +1,9 @@
+server {
+	listen 127.0.0.1:10048;
+	server_name i.krz.sh;
+	include custom.d/basic.conf;
+	include custom.d/security/content-security-policy.conf;
+	root /var/www/i/;
+	autoindex on;
+	location / { try_files $uri $uri/ =404; }
+}
diff --git a/linux/nginx/etc/nginx/conf.d/krz.sh.conf b/linux/nginx/etc/nginx/conf.d/krz.sh.conf
index 0169dbe..dcf5f62 100644
--- a/linux/nginx/etc/nginx/conf.d/krz.sh.conf
+++ b/linux/nginx/etc/nginx/conf.d/krz.sh.conf
@@ -4,5 +4,6 @@ server {
 	root /var/www/krz.sh/;
 	absolute_redirect off;
 	include custom.d/basic.conf;
+	include custom.d/security/content-security-policy-krz.conf;
 	location / { try_files $uri $uri/ =404; }
 }
diff --git a/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf b/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf
index ee53aed..b27cbf0 100644
--- a/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf
+++ b/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf
@@ -50,7 +50,7 @@ map $sent_http_content_type $x_frame_options {
 # literal newlines, which nginx emitted verbatim -- a folded, malformed header
 # that clients saw as empty. Policies must be ONE line.
 map $sent_http_content_type $content_security_policy {
-  ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; upgrade-insecure-requests";
+  ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'";
 }
 
 # cleberg.* only. Mirrors what the site actually loads, verified by grepping
@@ -69,6 +69,23 @@ map $sent_http_content_type $content_security_policy_cmc {
   ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' https://img.cleberg.net data:; script-src 'self' https://bubbles.town; connect-src 'self' https://bubbles.town; style-src 'self' https://cleberg.net; font-src 'self' https://cleberg.net; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'";
 }
 
+# krz.sh only. Derived from the served HTML after the img.cleberg.net removal
+# (2026-08-03), counted with an HTML parser:
+#   14 <img src>              -> https://i.krz.sh   (the new krz-side image host)
+#   12 <link rel=stylesheet>  -> https://krz.sh     (ABSOLUTE, not relative)
+#   12 <link rel=icon>        -> https://krz.sh     (covered by img-src)
+#    0 scripts, inline or external; 0 inline styles.
+#
+# `https://krz.sh` is listed explicitly because those URLs are absolute: on a
+# future krz.sh onion they would be cross-origin and 'self' would block them.
+# Same reasoning as the cmc policy above.
+#
+# script-src stays 'self' rather than 'none': the site ships no scripts today,
+# but 'none' would break the first one added, in a way that is easy to misread.
+map $sent_http_content_type $content_security_policy_krz {
+  ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' https://krz.sh https://i.krz.sh data:; style-src 'self' https://krz.sh; font-src 'self' https://krz.sh; script-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'";
+}
+
 # For apps that ship inline <script>/<style>/style="" and cannot use the strict
 # policy. Currently only office.zerolabs.sh (5 files with inline <script>,
 # 3 with <style>, 2 with style attributes). Identity-neutral -- no host is
@@ -79,7 +96,7 @@ map $sent_http_content_type $content_security_policy_cmc {
 # strictly worse. The upgrade path is per-file hashes or nonces; that needs
 # changes to the app, not to nginx.
 map $sent_http_content_type $content_security_policy_inline {
-  ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; upgrade-insecure-requests";
+  ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'";
 }
 
 # --- Proxied third-party apps -------------------------------------------
diff --git a/linux/nginx/etc/nginx/custom.d/security/content-security-policy-krz.conf b/linux/nginx/etc/nginx/custom.d/security/content-security-policy-krz.conf
new file mode 100644
index 0000000..6cbd8ea
--- /dev/null
+++ b/linux/nginx/etc/nginx/custom.d/security/content-security-policy-krz.conf
@@ -0,0 +1,10 @@
+# Content-Security-Policy for krz.sh itself.
+#
+# Uses $content_security_policy_krz, which names https://i.krz.sh (the krz-side
+# image host) and https://krz.sh (its own absolute asset URLs). Both are `krz`
+# surfaces, so this stays identity-clean -- it names no `cleberg` host.
+#
+# Other krz vhosts should use custom.d/security/content-security-policy.conf
+# (the strict neutral one); this variant exists only because krz.sh loads
+# cross-origin images and uses absolute self-URLs.
+add_header Content-Security-Policy $content_security_policy_krz always;