From 90a151348e344b7002b8126aa8d235bda88879ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 13 Dec 2025 17:19:16 -0500 Subject: [PATCH 1/5] fix: moving topic to cid=-1 will remove it from list --- public/src/client/category/tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js index 42160e6e6a..f298cbe85d 100644 --- a/public/src/client/category/tools.js +++ b/public/src/client/category/tools.js @@ -285,7 +285,7 @@ define('forum/category/tools', [ } async function onTopicMoved(data) { - if (ajaxify.data.template.category) { + if (ajaxify.data.template.category || String(data.toCid) === '-1') { getTopicEl(data.tid).remove(); } else { const category = await api.get(`/categories/${data.toCid}`); From 5ae8d553ed1ba276b92afeb2b32b26623209d225 Mon Sep 17 00:00:00 2001 From: Shlomo <78599753+ShlomoCode@users.noreply.github.com> Date: Mon, 15 Dec 2025 20:16:38 +0200 Subject: [PATCH 2/5] fix: disallow inline viewing of unsafe files (#13833) --- src/middleware/index.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/middleware/index.js b/src/middleware/index.js index 14cb3138e1..02024b8a19 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -273,10 +273,19 @@ middleware.buildSkinAsset = helpers.try(async (req, res, next) => { middleware.addUploadHeaders = function addUploadHeaders(req, res, next) { // Trim uploaded files' timestamps when downloading + force download if html let basename = path.basename(req.path); - const extname = path.extname(req.path); - if (req.path.startsWith('/uploads/files/') && middleware.regexes.timestampedUpload.test(basename)) { - basename = basename.slice(14); - res.header('Content-Disposition', `${extname.startsWith('.htm') ? 'attachment' : 'inline'}; filename="${basename}"`); + const extname = path.extname(req.path).toLowerCase(); + const unsafeExtensions = [ + '.html', '.htm', '.xhtml', '.mht', '.mhtml', '.stm', '.shtm', '.shtml', + '.svg', '.svgz', + '.xml', '.xsl', '.xslt', + ]; + const isInlineSafe = !unsafeExtensions.includes(extname); + const dispositionType = isInlineSafe ? 'inline' : 'attachment'; + if (req.path.startsWith('/uploads/files/')) { + if (middleware.regexes.timestampedUpload.test(basename)) { + basename = basename.slice(14); + } + res.header('Content-Disposition', `${dispositionType}; filename="${basename}"`); } next(); From 9f729964161bb12b1b860b18c46379e24132d2f6 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Dec 2025 10:56:57 -0500 Subject: [PATCH 3/5] feat: stop extraneous vote and tids_read data from being saved for remote users --- src/posts/votes.js | 20 ++++--- src/topics/unread.js | 2 +- .../4.7.1/remove_extraneous_ap_data.js | 24 ++++++++ test/activitypub/notes.js | 55 +++++++++++++++---- 4 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 src/upgrades/4.7.1/remove_extraneous_ap_data.js diff --git a/src/posts/votes.js b/src/posts/votes.js index 599c4f92d0..d9487b1dc2 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -177,16 +177,18 @@ module.exports = function (Posts) { } const now = Date.now(); - if (type === 'upvote' && !unvote) { - await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); - } else { - await db.sortedSetRemove(`uid:${uid}:upvote`, pid); - } + if (utils.isNumber(uid)) { + if (type === 'upvote' && !unvote) { + await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); + } else { + await db.sortedSetRemove(`uid:${uid}:upvote`, pid); + } - if (type === 'upvote' || unvote) { - await db.sortedSetRemove(`uid:${uid}:downvote`, pid); - } else { - await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + if (type === 'upvote' || unvote) { + await db.sortedSetRemove(`uid:${uid}:downvote`, pid); + } else { + await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + } } const postData = await Posts.getPostFields(pid, ['pid', 'uid', 'tid']); diff --git a/src/topics/unread.js b/src/topics/unread.js index 4dd5a94435..ed93f19abf 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -290,7 +290,7 @@ module.exports = function (Topics) { }; Topics.markAsRead = async function (tids, uid) { - if (!Array.isArray(tids) || !tids.length) { + if (!Array.isArray(tids) || !tids.length || !utils.isNumber(uid)) { return false; } diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js new file mode 100644 index 0000000000..99b5d9de4d --- /dev/null +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -0,0 +1,24 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + +module.exports = { + name: 'Remove extraneous upvote and tids_read data for remote users', + timestamp: Date.UTC(2025, 11, 11), + method: async function () { + const { progress } = this; + await batch.processSortedSet('usersRemote:lastCrawled', async (uids) => { + const readKeys = uids.map(uid => `uid:${uid}:tids_read`); + const voteKeys = uids.map(uid => `uid:${uid}:upvote`); + + const combined = readKeys.concat(voteKeys); + + await db.deleteAll(combined); + }, { + batch: 500, + progress, + }); + + }, +}; diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index b43f2da49f..1eba189b92 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -432,6 +432,7 @@ describe('Notes', () => { describe('Create', () => { let uid; + let cid; before(async () => { uid = await user.create({ username: utils.generateUUID() }); @@ -451,6 +452,17 @@ describe('Notes', () => { assert.strictEqual(cid, -1); }); + it('should not append to the tids_read sorted set', async () => { + const { note, id } = helpers.mocks.note(); + const { activity } = helpers.mocks.create(note); + + await db.sortedSetAdd(`followersRemote:${note.attributedTo}`, Date.now(), uid); + await activitypub.inbox.create({ body: activity }); + + const exists = await db.exists(`uid:${note.attributedTo}:tids_read`); + assert(!exists); + }); + it('should create a new topic in a remote category if addressed (category same-origin)', async () => { const { id: remoteCid } = helpers.mocks.group(); const { note, id } = helpers.mocks.note({ @@ -467,40 +479,63 @@ describe('Notes', () => { }); it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () { - this.timeout(30000); - const start = Date.now(); const { id: remoteCid } = helpers.mocks.group({ id: `https://example.com/${utils.generateUUID()}`, }); - console.log('1', Date.now() - start); const { note, id } = helpers.mocks.note({ audience: [remoteCid], }); - console.log('2', Date.now() - start); const { activity } = helpers.mocks.create(note); - console.log('3', Date.now() - start); try { await activitypub.inbox.create({ body: activity }); } catch (err) { - console.log('error in test', err.stack); assert(false); } - console.log('4', Date.now() - start); assert(await posts.exists(id)); - console.log('5', Date.now() - start); const cid = await posts.getCidByPid(id); - console.log('6', Date.now() - start); assert.strictEqual(cid, -1); }); }); + + describe('(Like)', () => { + let pid; + let voterUid; + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID() })); + const { postData } = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + pid = postData.pid; + const object = await activitypub.mocks.notes.public(postData); + const { activity } = helpers.mocks.like({ object }); + voterUid = activity.actor; + await activitypub.inbox.like({ body: activity }); + }); + + it('should increment a like for the post', async () => { + const voted = await posts.hasVoted(pid, voterUid); + const count = await posts.getPostField(pid, 'upvotes'); + assert(voted); + assert.strictEqual(count, 1); + }); + + it('should not append to the uid upvotes zset', async () => { + const exists = await db.exists(`uid:${voterUid}:upvote`); + assert(!exists); + }); + }); }); describe('Announce', () => { let cid; before(async () => { - ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + ({ cid } = await categories.create({ name: utils.generateUUID() })); }); describe('(Create)', () => { From 9f94a72117d6dcd661fd52e017c40c34efa15f54 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Dec 2025 11:09:13 -0500 Subject: [PATCH 4/5] fix: increment progress on upgrade script --- src/upgrades/4.7.1/remove_extraneous_ap_data.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js index 99b5d9de4d..7f2d0f7e52 100644 --- a/src/upgrades/4.7.1/remove_extraneous_ap_data.js +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -15,6 +15,7 @@ module.exports = { const combined = readKeys.concat(voteKeys); await db.deleteAll(combined); + progress.incr(combined.length); }, { batch: 500, progress, From b1fc5bfdaab55d0b428767b9a2640c77fac5ca07 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 11 Dec 2025 11:10:29 -0500 Subject: [PATCH 5/5] fix: wrong increment value --- src/upgrades/4.7.1/remove_extraneous_ap_data.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js index 7f2d0f7e52..e32590cb01 100644 --- a/src/upgrades/4.7.1/remove_extraneous_ap_data.js +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -15,11 +15,10 @@ module.exports = { const combined = readKeys.concat(voteKeys); await db.deleteAll(combined); - progress.incr(combined.length); + progress.incr(uids.length); }, { batch: 500, progress, }); - }, };