From 7ebd069cf750c0b2a25e125aca6fd676f2fbd8f3 Mon Sep 17 00:00:00 2001 From: Claudiu Schuster Date: Thu, 3 Sep 2026 15:19:07 +0200 Subject: [PATCH] Add canonical host and security.txt safeguards Keep the OSS origin and Cloudflare security disclosure paths aligned while redirecting every non-apex host to the canonical HTTPS site. --- scripts/build-site.sh | 2 ++ scripts/check-site.py | 39 ++++++++++++++++++++++++++++++++++- site/.htaccess | 2 +- site/.well-known/security.txt | 5 +++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 site/.well-known/security.txt diff --git a/scripts/build-site.sh b/scripts/build-site.sh index 8a8bc40..4822479 100755 --- a/scripts/build-site.sh +++ b/scripts/build-site.sh @@ -19,6 +19,7 @@ esac rm -rf -- "$output" mkdir -p \ + "$output/.well-known" \ "$output/assets/brand" \ "$output/assets/projects" \ "$output/assets/scripts" \ @@ -41,6 +42,7 @@ copy_file .htaccess .htaccess copy_file robots.txt robots.txt copy_file sitemap.xml sitemap.xml copy_file site.webmanifest site.webmanifest +copy_file .well-known/security.txt .well-known/security.txt copy_file assets/brand/oss-singularity-mark.svg assets/brand/oss-singularity-mark.svg copy_file assets/projects/pdrive-control-center-v080.webp assets/projects/pdrive-control-center-v080.webp copy_file assets/projects/chatgpt-usage-v030.webp assets/projects/chatgpt-usage-v030.webp diff --git a/scripts/check-site.py b/scripts/check-site.py index 1cddcaf..2dbca03 100755 --- a/scripts/check-site.py +++ b/scripts/check-site.py @@ -5,6 +5,7 @@ import json import sys +from datetime import datetime, timedelta, timezone from html.parser import HTMLParser from pathlib import Path from urllib.parse import urlsplit @@ -71,7 +72,8 @@ def main() -> int: fail(f"missing build directory {root}") required = { - ".htaccess", "404.html", "dist-manifest.sha256", "index.html", + ".htaccess", ".well-known/security.txt", "404.html", + "dist-manifest.sha256", "index.html", "robots.txt", "sitemap.xml", "site.webmanifest", "assets/brand/oss-singularity-mark.svg", "assets/projects/chatgpt-usage-v030.webp", @@ -141,6 +143,41 @@ def main() -> int: if 'name="robots" content="noindex"' not in (root / "404.html").read_text(encoding="utf-8"): fail("404 page must be noindex") + security_txt = (root / ".well-known/security.txt").read_text(encoding="utf-8") + if not security_txt.endswith("\n") or "\r" in security_txt: + fail("security.txt must be LF-terminated UTF-8 text") + security_fields: dict[str, list[str]] = {} + for line in security_txt.splitlines(): + if not line or line.startswith("#"): + continue + name, separator, value = line.partition(":") + if not separator or not value.strip(): + fail(f"malformed security.txt line: {line!r}") + security_fields.setdefault(name, []).append(value.strip()) + expected_security_fields = { + "Contact": ["mailto:mail@oss-singularity.io"], + "Canonical": ["https://oss-singularity.io/.well-known/security.txt"], + "Policy": ["https://github.com/oss-singularity/website/security/policy"], + "Preferred-Languages": ["en, de"], + } + for name, expected in expected_security_fields.items(): + if security_fields.get(name) != expected: + fail(f"security.txt {name} must be {expected[0]!r}") + expires_values = security_fields.get("Expires", []) + if len(expires_values) != 1: + fail("security.txt must contain exactly one Expires field") + try: + expires = datetime.fromisoformat(expires_values[0].replace("Z", "+00:00")) + except ValueError as error: + fail(f"security.txt Expires is not RFC 3339: {error}") + if expires.tzinfo is None: + fail("security.txt Expires must include a timezone") + now = datetime.now(timezone.utc) + if expires <= now: + fail("security.txt has expired") + if expires - now > timedelta(days=366): + fail("security.txt Expires must be less than one year ahead") + if html_bytes > 35_000: fail(f"HTML budget exceeded: {html_bytes} bytes") css_bytes = (root / "assets/styles/site-v2.css").stat().st_size diff --git a/site/.htaccess b/site/.htaccess index 781057a..f2591bd 100644 --- a/site/.htaccess +++ b/site/.htaccess @@ -5,7 +5,7 @@ ErrorDocument 404 /404.html RewriteEngine On RewriteCond %{HTTPS} !=on [OR] - RewriteCond %{HTTP_HOST} ^www\.oss-singularity\.io$ [NC] + RewriteCond %{HTTP_HOST} !^oss-singularity\.io$ [NC] RewriteRule ^ https://oss-singularity.io%{REQUEST_URI} [R=301,L,NE] diff --git a/site/.well-known/security.txt b/site/.well-known/security.txt new file mode 100644 index 0000000..b66a52c --- /dev/null +++ b/site/.well-known/security.txt @@ -0,0 +1,5 @@ +Contact: mailto:mail@oss-singularity.io +Expires: 2027-08-31T23:59:59Z +Preferred-Languages: en, de +Canonical: https://oss-singularity.io/.well-known/security.txt +Policy: https://github.com/oss-singularity/website/security/policy