Security Audit Fixes, Test Coverage Improvements, and Docker Metadata Labels
✨ Features
- server: Added per-IP per-resource password attempt lockout - after 10 failed attempts the IP is locked out for 15 minutes with a
Retry-Afterheader; IPs stored as ephemeral HMAC-SHA256 hashes; configurable viaPASSWORD_MAX_ATTEMPTSandPASSWORD_LOCKOUT_MS
🔒 Security
- server: Updated
honofrom4.12.12to4.12.14to fix HTML injection via improperly handled JSX attribute names in SSR (GHSA-458j-xx4x-4375) - infra: Added
pnpm.overridesforesbuild(>=0.25.0),vite(>=6.4.2), andfast-xml-parser(>=5.7.0) to patch transitive vulnerabilities in dev/docs dependencies (GHSA-67mh-4wv8-2f99, GHSA-4w7w-66w2-5vf9, GHSA-gh4j-gqv2-49f6) - crypto: Tightened Argon2id-to-PBKDF2 fallback logic - the fallback now only triggers on WASM availability errors (
CompileError,LinkError, or matching message) and propagates all other crypto errors instead of silently swallowing them - crypto: Increased Argon2id parameters from 19 MiB / 2 iterations to 64 MiB / 3 iterations, matching OWASP's strong recommendation and significantly raising GPU brute-force cost for new password-protected uploads
- crypto: Increased HKDF salt length from 16 to 32 bytes for new uploads, matching the RFC 5869 recommendation to use a salt equal to the hash output length - legacy 16-byte salts from existing uploads are still accepted
- crypto: Added stream truncation detection to
createDecryptStream- callers can passexpectedPlaintextSize(from authenticated metadata) to detect a malicious server delivering fewer records than were encrypted - server: Added
Referrer-Policy: no-referrerHTTP header to prevent URL fragments from leaking viaRefererheader in misconfigured or future environments - web: Added
<meta name="referrer" content="no-referrer">toindex.htmlas defense-in-depth for the referrer policy - web: URL fragment (encryption key) is now removed from the browser address bar via
history.replaceStateonce decryption begins, preventing key leakage through browser history - web: Note uploads now use Argon2id for password key derivation - previously PBKDF2 was always used for notes due to a missing argument
- crypto: Removed Argon2id-to-PBKDF2 upload fallback entirely - if Argon2id WASM fails during an upload, an error is thrown instead of silently downgrading to PBKDF2 ("fail secure"); PBKDF2 decryption for existing uploads is unaffected
- web: Added DOMPurify sanitization of highlight.js output before
dangerouslySetInnerHTMLin code notes - defense-in-depth against any future upstream hljs vulnerability - web: Added
rehype-sanitizeplugin to ReactMarkdown rendering in notes - prevents XSS from future react-markdown upstream changes that could enable raw HTML - web: URL fragment (encryption key) is now removed from the browser address bar via
history.replaceStateinNoteViewimmediately at page mount - previously only the Download page had this protection - server: Fixed IP extraction when
TRUST_PROXY=true- previously the leftmost (client-controlled) value fromX-Forwarded-Forwas used, allowing clients to spoof their IP and bypass rate limiting and upload quotas; now uses the rightmost (proxy-appended) value - server: Fixed S3 download with
S3_PUBLIC_URLconfigured - previously a permanent public URL was returned, remaining valid indefinitely after DB record deletion and bypassing expiry/download-limit enforcement; now always uses presigned URLs with a TTL - web: Replaced deprecated
apple-mobile-web-app-capablemeta tag with the standardmobile-web-app-capableequivalent - eliminates browser console warning; the PWA manifestdisplay: standalonealready handles standalone mode on modern browsers
🎨 Improvements
- web: Password prompt now shows a translated "too many attempts" error when the server returns 429 instead of switching away from the password screen
🔧 CI/CD
- infra: Added OCI standard labels to Docker images via
docker/metadata-action@v5- setstitle,description,url,source,version,revision,created,vendor, andlicensesfor better registry compatibility and Renovate/Dependabot integration
🧪 Tests
- crypto: Expanded to 129 tests with 100% coverage - added security-property tests for HKDF domain separation, ECE reorder/truncation attacks, Argon2id error propagation, PBKDF2 known-answer verification, and legacy salt backward compatibility.
- server: Added 24 integration tests for the chunked upload flow (
/init,/chunk,/finalize), password-attempt lockout (429 +Retry-After), and invalid input handling acrossmeta.ts,password.ts, andnote.tsroutes. - server: Added 5 tests for
startCleanupJob(interval, stop function, logging, error recovery) - bringingcleanup.tsto 100%. - server: Brought
upload-validation.tsandquota.tsto 100% - covers allcheck(),getStatus(), 413 middleware, DB key restoration, and interval behavior (rotation, expiry cleanup). - infra: Added
vitest.config.tsforserverandclient; updatedvite.config.tsforweb- all with scopedcoverage.includeand explicit excludes for untestable files (browser workers, WASM, S3 backend, app entrypoints).
🐳 Docker
- Image:
skyfay/skysend:v2.5.0 - Also tagged as:
latest,v2 - Platforms: linux/amd64, linux/arm64