Security
The threat model is a static site with no server of its own: nothing here listens on a port, so what remains to get wrong is what the pages carry, what the working directory holds, and the one thing that arrives from outside -- a post sent over the SSH the machine already has.
- Content-Security-Policy, delivered as a meta tag, because a static host may not let you set headers. Fonts are self-hosted, so no third-party origin is needed for them. A media player's origin is admitted only on the pages that actually embed one -- the build knows which posts carry a provider, so an archive page that embeds nothing allows nothing; YouTube's two origins are the exception and are allowed everywhere. The one inline script is allowlisted by content hash, see The client side.
- Draft URLs carry a
SecureRandomtoken andnoindex. That makes a preview link unguessable and keeps it out of search results. It is not access control: anyone the link reaches can read the draft, which is the point of being able to send one. - Escaping is consistent for everything that came off a network -- Fediverse display names, avatar URLs, reply text -- in both the HTML the build writes and the DOM the client builds. The one exception is Mastodon's own sanitized status HTML, see The client side.
- Input that came off a wire enters at one place,
scripts/receive.sh, which listens on nothing: it runs as the forced command of an SSH key. What a stranger chooses is a filename and a stream of bytes, so a name may carry no path, no leading dot and no control character, the read is bounded byBLOGSH_MAX_MBbefore anything lands, and the markdown is parsed confined -- a picture may be named only by a bare filename, becausewould otherwise be read into the post and published. There is no archive format anywhere in it, which is the whole reason there is nothing to unpack. env.shholds the live credentials, stays out of git, and is mode600. The wizards leave a.bakof whatever they rewrote at the same mode, so it still holds the previous tokens -- remove it once one has been rotated.- No third-party tracking goes into post data, and the sidebar widgets are fetched server-side by cron rather than by the visitor's browser, so reading a page contacts one origin.
