Nothing set a CSP, a referrer policy, nosniff or a framing rule, so the
app relied entirely on the same-origin policy.
The referrer policy is the one with a concrete leak behind it: a share
token is part of the URL path, so following any outbound link from a
shared entry handed a third party a working link to a private entry.
`no-referrer` closes that.
The layer is `if_not_present`, so the media routes keep the stricter
sandbox CSP they set for themselves.
The MIME came straight from the client's multipart Content-Type and was
handed back verbatim with Content-Disposition: inline. A file uploaded as
text/html therefore ran as script on the diary's own origin, with the
session cookie's full authority — and share::serve_media exposed the same
path to anyone holding a share link, without a session at all.
Uploads are now pinned to an allowlist of types that cannot execute
(image except SVG, video, audio, text/plain, application/pdf); everything
else is stored and served as application/octet-stream + attachment. The
check runs on the way out as well as on the way in, so rows written before
this commit are covered too. Media responses also carry nosniff and a
sandbox CSP.
Adds :today, :next/:prev, :first/:last, :random, :clear, :reload, :copy,
:export, :stats and :theme, plus bangs on :w, :wq and :e. Every command now
declares a group so :help can lay them out under headings.
The help sheet flowed its columns wider than the sheet and clipped the
descriptions; it is newspaper columns now, and it lists the ! variants.
CodeMirror's base theme styles panels for a light editor with selectors the
app stylesheet cannot outrank, so the vim `:` prompt rendered black on black.
Those rules move into the editor's own theme, which does win, and the theme
declares itself dark so the light defaults stop applying at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01476ZKdB4k3rPDwvVhXcnDg
CodeMirror injects its own `.cm-cursor` rule after our stylesheet, so the
equally specific override lost and the caret painted black on a dark
background. Qualify the selector, same as `.cm-activeLine`.
`:q!` reached the command table as the literal name `quit!`, which nothing
matched. Commands now carry an optional `bang` variant, and `runCommand`
splits a trailing `!` off any head before looking it up.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01476ZKdB4k3rPDwvVhXcnDg
Mocha with the palette's red as the accent is now the default; the old
green scheme moves to `:set theme=green` alongside amber and ice.
Two readability fixes in the editor:
- The vim block cursor paints the character inside itself in that
character's own syntax colour, so an accent-coloured glyph — a
heading's `#`, a link — vanished against the accent-coloured block.
The glyph is now forced to the background colour.
- CodeMirror injects its own `.cm-activeLine` rule after this stylesheet,
so the equally specific override never applied and the current line
rendered as a bright blue box. Qualified the selector and toned the
band down to a faint tint of the foreground.
Insert mode also gets a 2px bar instead of the browser hairline caret.
Three-stage build: bun compiles the frontend, cargo embeds it into the
release binary, and the runtime stage carries only that binary on
debian:trixie-slim as an unprivileged user.
compose.yml pins DIARY_BIND and DIARY_DATA_DIR over whatever .env says,
keeps state in a named volume, publishes on localhost only and probes the
port with bash so the image needs no curl. compose.override.yml.example
shows the traefik overlay for the personal host.
Single-user diary served as one Rust binary with the Svelte frontend
embedded.
- axum 0.8 + SQLite (sqlx), migrations run at startup
- stateless HMAC session cookie; credentials from .env, compared in
constant time
- FTS5 full-text search, user input rewritten into quoted prefix terms
- media uploads linked to entries by scanning bodies for /api/media/<id>,
which scopes share-token media access and entry deletion
- unguessable share links (192-bit token) serving a public read-only page
- vim keybindings via CodeMirror plus a modal browse layer and : command
line; disabled on touch devices