- Security hardening across the API, auth and data layer.
- Node.js runtime upgraded to the latest security release. npm dependencies updated to their newest versions.
- Repository hygiene.
Release notes, security updates, and feature milestones.
/contact replaces every published mailbox: a form (category, optional e-mail, subject, message) creates a ticket with a short public reference such as F0CK-7F3K9Q. Reporters keep the number to check the status and a one-time reply token to add information; a moderator answers by e-mail only when an address was given. Limits: 3 new tickets per hour per IP, honeypot + minimum fill time + the site's existing hCaptcha.v1:<iv>:<tag>:<ciphertext>, new IV per record). Writing is fail-closed: with a missing or malformed CONTACT_ENCRYPTION_KEY no ticket is created and the reason is logged. IPs are never stored - only sha256(CONTACT_IP_SALT + ip). See wiki/guides/Contact-Tickets.md.status: "open" is part of the update filter), so two moderators clicking at once produce exactly one winner and the loser gets a 409 naming the current owner. A claimed ticket is read-only for everybody else; admins can release it back to the queue (logged).To header - with subject [f0ck Contact] <Category> — <ticket number>, a ~200 character message preview and a deep link to https://f0ck.org/moderation?tab=contact&ticket=<id>. Mail failures never block ticket creation.monero:<address>?tx_amount=<amount> URI (no payment ID needed). Users can attach an optional transaction hash and press "I have sent the payment", which opens a pending ticket that freezes the XMR amount. An admin confirms or rejects it in the Moderation Dashboard → Monero tab, and confirming sets the same premium fields the Stripe webhook writes (isActive, plan, startDate, endDate for the plan's duration)..env.example for the full list.MONERO_WALLET_ADDRESS is validated offline with the full Base58 block codec, the network prefix and the Keccak checksum - everything validate_address on the wallet RPC checks. A typo that used to look fine is now a loud error naming the reason (bad_checksum, not_mainnet, integrated address) instead of money routed to a stranger. Integrated addresses are refused as the receive address because their embedded payment id collides with per-ticket tracking; subaddresses are accepted. Keccak-256 is implemented in-module (~60 lines of Keccak-f[1600]) rather than pulling in a dependency for one 69-byte digest.GET /api/monero/check. Admin/mod only. With MONERO_WALLET_RPC_URL set, the route asks a view-only monero-wallet-rpc (via incoming_transfers, matched on the frozen piconero amount, with a tx-hash match winning over an amount match) which open tickets have money waiting, and writes that into the ticket (lastCheckedAt, rpcMatched*) so staff do not have to open a block explorer. A hit never grants premium and never changes a ticket's status - it is a hint, because several tickets can share one amount. Without the RPC URL the route answers 501 and says exactly that, and staff approval stays the complete path. MONERO_MIN_CONFIRMATIONS (default 10) and MONERO_RPC_TIMEOUT_MS (default 15000) tune the lookup.src/. tests/monero-wallet-rpc.test.ts sweeps the tree for the wallet-RPC spend verbs and the payout vocabulary, and asserts the operator's secret view key (read from the gitignored deployment env files, never hardcoded) appears in no code file, no doc and no tracked env template. Operator keys live in .env.local / .env.production (both gitignored); .env.example documents them and deliberately ships MONERO_SECRET_VIEW_KEY= empty.monero-wallet-rpc (official Monero binaries v0.18.5.3, sha256-verified) runs next to the app as a systemd service on the deploy host, bound to 127.0.0.1:18083 with digest auth and no published port. The wallet is created from the operator's address and secret view key - no spend key on the server - and restores from a height ~4 weeks back (the age of the address), so the initial scan takes minutes and a few hundred megabytes instead of the whole chain; the wallet file stays in the kilobyte range and no node runs on the box. GET /api/monero/check now asks the wallet which open tickets have money waiting (amount match, tx-hash match wins, 10 confirmations) and marks the ticket as a hint - a human still confirms it. The manual approval path stays the fallback and answers exactly that when the RPC is unreachable.fetch cannot talk to monero-wallet-rpc: undici refuses URLs that carry credentials, and epee scopes the digest challenge nonce to the TCP connection that received it, so a correctly signed retry on a fresh connection is still rejected with 401. The client parses the credentials out of the URL, signs the challenge (RFC 2617, pinned against the published example vector in the tests) and pins both requests to one keep-alive socket through a single-socket node:http agent. Found against the real binary during deployment - the mocked unit tests alone could not see it.NEXT_PUBLIC_VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT); without them push is skipped with a warning while in-app notifications keep working. Use the keys from .env.example rather than ones from another project.https://t.me/f0ckorg opens the external-services row, ahead of the ShareX/Flameshot host, ZNC bouncer and TeamSpeak entries.bad_checksum because the payment id was never covered. The test suite stayed green only because the 106-character test vector had been invented to fit the broken rule.newassets domain, so verification failed. Allowed in both the main CSP and the proxy; the rotated cocks secret is picked up again. An invalid secret is now called out loudly in the server log instead of hiding behind a generic error./, which pushed /tags and friends out of Google's index. Every public page now declares its own canonical.hideAds: false default are fixed on server start, no manual step.background shorthand was resetting the text clip, producing colored blocks. All saved formats still work.callbackUrl on /login is now validated to be a relative path before the post-login jump, the same guard the register page already had. A crafted link could land a freshly authenticated user on an external phishing clone; that path is closed now.MESSAGE_ENCRYPTION_KEY environment variable and never leaves the server, so a database leak hands you blobs instead of conversations. Old unencrypted messages are re-encrypted silently the first time they are read. Transport stays HTTPS/TLS on both hops, and DM notifications never carry message text anymore. Set MESSAGE_ENCRYPTION_REQUIRED=true to make sending fail loudly instead of falling back when the key is missing. Full writeup in wiki/guides/Message-Encryption.md./f0ck pages listed.cloudflareinsights.com added to the script and connect sources..f0ck.org. It reads NEXTAUTH_COOKIE_DOMAIN now and those hostnames are allowed origins.SECURITY_ROTATION.md — rotate them if you run a fork.next.config.mjs, covering Stripe, Discord, Giphy and websocket connections.tagtype parameter on the API to match./api/public/status with active users, posts, comments and tags for anyone who wants to build something on the API.raw option. Upgraded to 9.0.1, and with it all 23 dependabot alerts are closed.id vs numericId field divergence in Post model. All comment API routes now consistently resolve and return numericId as canonical post identifier./api/comments now search by { $or: [{ numericId }, { id }] } instead of only { id }, ensuring correct post resolution regardless of which field matches.postObjectId?.id (which returned ObjectId hex string) to use the actual numeric post ID.numericId over _id for consistent URL generation.normalizeInputTags, validateUploadParams, client-side validation in FileList, BatchTagging, upload page).min-w-0, break-words, truncate classes to prevent long titles/content from breaking layout.numericId for navigation and _id for fallback lookup.noindex for unsafe content -- all posts are now indexable.hover:-translate-y lift animations from filter containers and buttons on /posts/, /tags/, and /users/ pages for a cleaner, more static appearance.<Footer /> component to /tags/ page./moderation/public with read-only activity wall./api/moderation/public-activity for anonymous access to moderation actions.video/x-mov MIME type support for .mov video uploads.CHANGELOG.md cannot be read from the filesystem./api/tags and persists additions with PUT /api/posts/[id]/tags; spacebar no longer triggers tag creation.$in query instead of N+1 findOne calls and matches case-insensitively./api/user profile updates and pool membership changes.a.pipeThrough is not a function crash by aligning processUpload to the existing Readable.toWeb(Readable.from(...)) pattern already used in /api/upload and assemble-chunks routes, restoring PNG/Web Stream compatibility with file-type v22.meta.json values (tags, contentRating) no longer crash chunk assembly; tags are validated as arrays and content ratings are coerced to the supported set before being persisted.image/x-png is now whitelisted across upload entry points, preventing false rejections on older browser-reported PNG types./f0ck/blog/ listing and detail pages with RSS feed at /api/blog/rss.document.body to prevent z-index stacking issues.getAdjacentPosts no longer filters by content rating -- navigation always finds the truly adjacent post regardless of rating.flex-shrink-0 to prevent button compression on smaller screens.z-[10000] to render above emoji picker.PUT /api/user with 500ms debounce and status feedback.GET /api/user now returns 500 because the aggregation pipeline mixed inclusion (password: 1) with exclusion (password: 0) in the same $project stage. Fixed by removing the inclusion; only password: 0 is used, preventing MongoServerError: Invalid $project. Ref: src/app/api/user/route.ts.onClose is not a function crash fixed in both EmojiPicker (src/components/EmojiPicker.tsx) and GifSelector (src/components/GifSelector.tsx): onClose is now an optional prop with a default no-op, so components render safely even when the prop is omitted.src/app/comments/components/CommentList.tsx): EmojiPicker and GifSelector instances in the comment creation box now receive the correct onClose callback and no longer depend on a non-existent wrapper onClick handler.src/app/posts/components/PostDetails.tsx): EmojiPicker and GifSelector both receive onClose callbacks, matching the implementation already present in the inline Comment reply box.src/app/posts/components/PostsPage.tsx): Left arrow button was labelled "Next" but navigated to the previous page; right arrow button was labelled "Previous" but navigated forward. Both labels swapped to read Previous / Next, matching the Math.max(1, p-1) and Math.min(totalPages, p+1) action handlers.src/app/posts/components/PostNavigation.tsx): Previous button was wired to nextPostId (higher-ID / newer post, not the older one) and Next button was wired to previousPostId. JSX conditionals swapped so that Previous is shown only when previousPostId exists and navigates to the older post, while Next is shown only when nextPostId exists and navigates to the newer post.src/components/Footer.tsx); TeamSpeak 3 ts3server://ts.f0ck.org link retained as the sole external chat link.images.qualities: [75, 95] added to next.config.mjs so that quality={95} used in PostDetails.tsx is an allowed value and the Next.js build/DevTools warning disappears.AccountCard initial avatarUrl state (src/app/account/components/AccountCard.tsx) falls back to session?.user?.avatar (the value already known to NextAuth at login), preventing a flash of the default avatar before the /api/user profile API completes.src/app/sitemap.ts so that Google and other search-engines can discover /posts, /comments, /account, /moderation, /f0ck/changelog, /rules, /terms, and /help -- previously only the homepage was listed in any indexable source.src/app/f0ck/changelog/page.tsx) to include an absolute project-root fallback, so CHANGELOG.md reliably loads even in standalone Docker/prod contexts where process.cwd() points to the server working directory rather than the source tree root.env var fallback + DNS resolution checks; production data-leak (console.log) removed from PostDetails.tsx; ModActionsPanel action whitelist via TypeScript as const.let to const x8; 4 HTML-entity escapes; 4 require() to ESM imports; 11 react-hooks/exhaustive-deps fixes.ErrorBoundary class component with getDerivedStateFromError + Try-Again reset; ThemeContext SSR hydration fixed; module-level side-effects removed from layout.tsx.normalizeInputTags helper added; tag persistence fallback (new Tag().save() when findOrCreate fails); chunk upload tags forwarded via meta.json side-channel; N+1 tag query batched to $in; Tag model regex accepts hyphens.LiveWall component with AbortController polling; QuickActions onTabChange wired; PostModerator 8 dead typeof toast !== 'undefined' guards removed; ModLog action types fixed (feature/unfeature to MARK_AS_AD/REMOVE_AD).CommentList; stale closure fixed in handleLoadMore; 3 replace() to replaceAll() fixes in GIF/content rendering; /post/[id] page renders inline CommentList; 1,798 lines of dead code removed across 6 old comment files.?rating= from URL; keyboard navigation fixed (A = older, D = newer); PostNavigation wrapped in Suspense boundary; getImageUrlWithCacheBuster regex fixed with g flag.CommentsPage ingestion path quarantined (no longer imported into posts detail routes); CommentModeration uses unified component; CommentFilter and CommentList refactored to shared component shape.AddToPoolModal enhanced (search, loading, error); Pool API permission checks with moderator override; contributor tracking on pool actions; Pools field added to User model; Pool stats on user profiles.type enum now includes 'meta' alongside general/character/copyright/artist.User model privacySettings now has showPools field with isProfilePrivate defaulting to false._maxAge: '0' added to profile-fetch requests to ensure uncached data.comments/filter-users route pruning; error-format contract explicitly checked.ModLog pipeline; creative writing filter response corrected.file-type upgraded from v21 to v22 with migration to new stream-based API (fileTypeFromStream).eslint upgraded from v9 to v10 with full Flat Config migration (languageOptions, globals, js.configs.recommended).typescript upgraded from 5.9 to 6.0.3.@types/react upgraded to 19.2.18, @types/react-dom to 19.2.6, @types/node to 25.7.0.eslint: 9.39.2 to 10.3.0file-type: 21.3.0 to 22.0.1 (breaking: stream-based API)typescript: 5.9.3 to 6.0.3next: 16.2.6 (unchanged, latest stable)react/react-dom: 19.2.6 (unchanged, latest stable)tailwindcss/@tailwindcss/postcss/@tailwindcss/forms: 4.3.0 (unchanged, latest stable)@types/react: 19.2.14 to 19.2.18@types/react-dom: 19.2.3 to 19.2.6@types/node: 25.6.2 to 25.7.0eslint-config-next: 16.2.6 (unchanged)eslint.config.mjs):
js.configs.recommended as base config.env, globals, parserOptions into unified languageOptions block.globals from globals npm package (globals.browser, globals.node, globals.es2021).@eslint/eslintrc import pattern.warn level for backward compatibility.file-type v22 Migration:
fileTypeFromBuffer() replaced with fileTypeFromStream(Readable.from(buffer)) in:
src/lib/upload.tssrc/app/api/upload/route.tssrc/app/api/upload/assemble-chunks/route.tssrc/app/api/user/avatar/route.tsimport { Readable } from 'stream' where needed.package.override:
glob-parent, send, mime, qs, path-parse, debug, ms, lodash.merge.glob-parent >= 6.0.2 (path traversal)send >= 1.2.0 (DoS)mime >= 4.0.0 (content-type sniffing)qs >= 6.14.0 (prototype pollution)path-parse >= 1.0.7 (ReDoS)debug >= 4.4.3 (ReDoS)ms >= 2.1.3 (ReDoS)lodash.merge >= 4.6.2 (prototype pollution)npm audit passes with 0 vulnerabilities.fileTypeFromBuffer calls now use v22-compatible fileTypeFromStream with Readable wrapper./f0ck as a new project-native landing route/f0ck/changelog as in-app release notes/changelog redirect route for consistent external entry points./changelog/f0ck/changelog/f0ck/api/logos for dynamic .png logo selection.newPostsToday and newPostsThisWeek.public/logos.favoriteCount and favoritesCount.liked_byfavorited_bydisliked_byusers/[username]/activity caused by duplicate imports.page, limit, hasMore) and load-more UI state drift.nodemailer to 8.0.5 (GHSA-vvjj-xcjg-gr5g).lastSeen update.ObjectId validation in reports API.en for semantic correctness and crawler/accessibility consistency.most_items and related query edge cases.Showing 22 of 22 versions