You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
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-After header; IPs stored as ephemeral HMAC-SHA256 hashes; configurable via PASSWORD_MAX_ATTEMPTS and PASSWORD_LOCKOUT_MS
🔒 Security
server: Updated hono from 4.12.12 to 4.12.14 to fix HTML injection via improperly handled JSX attribute names in SSR (GHSA-458j-xx4x-4375)
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 pass expectedPlaintextSize (from authenticated metadata) to detect a malicious server delivering fewer records than were encrypted
server: Added Referrer-Policy: no-referrer HTTP header to prevent URL fragments from leaking via Referer header in misconfigured or future environments
web: Added <meta name="referrer" content="no-referrer"> to index.html as defense-in-depth for the referrer policy
web: URL fragment (encryption key) is now removed from the browser address bar via history.replaceState once 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 dangerouslySetInnerHTML in code notes - defense-in-depth against any future upstream hljs vulnerability
web: Added rehype-sanitize plugin 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.replaceState in NoteView immediately 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 from X-Forwarded-For was 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_URL configured - 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-capable meta tag with the standard mobile-web-app-capable equivalent - eliminates browser console warning; the PWA manifest display: standalone already 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 - sets title, description, url, source, version, revision, created, vendor, and licenses for 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 across meta.ts, password.ts, and note.ts routes.
server: Added 5 tests for startCleanupJob (interval, stop function, logging, error recovery) - bringing cleanup.ts to 100%.
server: Brought upload-validation.ts and quota.ts to 100% - covers all check(), getStatus(), 413 middleware, DB key restoration, and interval behavior (rotation, expiry cleanup).
infra: Added vitest.config.ts for server and client; updated vite.config.ts for web - all with scoped coverage.include and explicit excludes for untestable files (browser workers, WASM, S3 backend, app entrypoints).