This repository has no description
Something went wrong. Try again.
3.2 kB · 96 lines
Python
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697# SPDX-License-Identifier: AGPL-3.0-or-later
from __future__ import annotations
import base64import hashlibimport html.parserimport pathlibimport sys
class InlineScriptParser(html.parser.HTMLParser): def __init__(self) -> None: super().__init__(convert_charrefs=False) self._collecting = False self._parts: list[str] = [] self.scripts: list[str] = []
def handle_starttag( self, tag: str, attrs: list[tuple[str, str | None]] ) -> None: if tag.lower() != "script": return if any(name.lower() == "src" for name, _ in attrs): return self._collecting = True self._parts = []
def handle_data(self, data: str) -> None: if self._collecting: self._parts.append(data)
def handle_entityref(self, name: str) -> None: if self._collecting: self._parts.append(f"&{name};")
def handle_charref(self, name: str) -> None: if self._collecting: self._parts.append(f"&#{name};")
def handle_endtag(self, tag: str) -> None: if tag.lower() == "script" and self._collecting: self.scripts.append("".join(self._parts)) self._collecting = False
def script_hashes(site_dir: pathlib.Path) -> list[str]: hashes: set[str] = set() for path in sorted(site_dir.rglob("*.html")): parser = InlineScriptParser() parser.feed(path.read_text(encoding="utf-8")) for script in parser.scripts: digest = hashlib.sha256(script.encode("utf-8")).digest() hashes.add("'sha256-" + base64.b64encode(digest).decode("ascii") + "'") return sorted(hashes)
def main() -> None: site_dir = pathlib.Path(sys.argv[1]) output = pathlib.Path(sys.argv[2]) script_sources = " ".join(script_hashes(site_dir)) csp = "; ".join( [ "default-src 'self'", f"script-src 'self' {script_sources}", "style-src 'self' 'unsafe-inline' https://fluxerstatic.com", "img-src 'self' data:", "font-src 'self' data: https://fluxerstatic.com", "connect-src 'self' https://api.github.com", "worker-src 'self'", "frame-src 'none'", "object-src 'none'", "base-uri 'self'", "form-action 'self'", "frame-ancestors 'none'", "upgrade-insecure-requests", ] ) output.write_text( "\n".join( [ 'add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;', 'add_header X-Content-Type-Options "nosniff" always;', 'add_header Referrer-Policy "strict-origin-when-cross-origin" always;', 'add_header X-Frame-Options "DENY" always;', 'add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;', f'add_header Content-Security-Policy "{csp}" always;', "", ] ), encoding="utf-8", )
if __name__ == "__main__": main()