Security Fix 5: Read-only roles can still update cards.

Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
This commit is contained in:
Lauri Ojansivu 2025-12-29 16:47:11 +02:00
parent 198509e760
commit 181f837d8c
6 changed files with 23 additions and 15 deletions

View file

@ -2408,7 +2408,7 @@ if (Meteor.isServer) {
*/
JsonRoutes.add('PUT', '/api/boards/:boardId/labels', function(req, res) {
const id = req.params.boardId;
Authentication.checkBoardAccess(req.userId, id);
Authentication.checkBoardWriteAccess(req.userId, id);
try {
if (req.body.hasOwnProperty('label')) {
const board = ReactiveCache.getBoard(id);

View file

@ -3936,7 +3936,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
const newSwimlaneId = req.body.newSwimlaneId;
const newListId = req.body.newListId;
let updated = false;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
if (req.body.title) {
// Basic client-side validation - server will handle full sanitization
@ -4292,8 +4292,8 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
);
}
if (newBoardId && newSwimlaneId && newListId) {
// Validate destination board access
Authentication.checkBoardAccess(req.userId, newBoardId);
// Validate destination board write access
Authentication.checkBoardWriteAccess(req.userId, newBoardId);
// Validate that the destination list exists and belongs to the destination board
const destList = ReactiveCache.getList({
@ -4409,7 +4409,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
const paramCardId = req.params.cardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const card = ReactiveCache.getCard(paramCardId);
Cards.direct.remove({
@ -4485,7 +4485,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
const paramListId = req.params.listId;
const paramCustomFieldId = req.params.customFieldId;
const paramCustomFieldValue = req.body.value;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const card = ReactiveCache.getCard({
_id: paramCardId,
listId: paramListId,
@ -4541,7 +4541,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramListId = req.params.listId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const card = ReactiveCache.getCard({
_id: paramCardId,
listId: paramListId,
@ -4580,7 +4580,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramListId = req.params.listId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const card = ReactiveCache.getCard({
_id: paramCardId,
listId: paramListId,

View file

@ -685,7 +685,7 @@ if (Meteor.isServer) {
JsonRoutes.add('POST', '/api/boards/:boardId/lists', function(req, res) {
try {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const board = ReactiveCache.getBoard(paramBoardId);
const id = Lists.insert({
title: req.body.title,
@ -731,7 +731,7 @@ if (Meteor.isServer) {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
let updated = false;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const list = ReactiveCache.getList({
_id: paramListId,
@ -871,7 +871,7 @@ if (Meteor.isServer) {
try {
const paramBoardId = req.params.boardId;
const paramListId = req.params.listId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
Lists.remove({ _id: paramListId, boardId: paramBoardId });
JsonRoutes.sendResult(res, {
code: 200,

View file

@ -545,7 +545,7 @@ if (Meteor.isServer) {
JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function(req, res) {
try {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const board = ReactiveCache.getBoard(paramBoardId);
const id = Swimlanes.insert({
@ -581,7 +581,7 @@ if (Meteor.isServer) {
try {
const paramBoardId = req.params.boardId;
const paramSwimlaneId = req.params.swimlaneId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const board = ReactiveCache.getBoard(paramBoardId);
const swimlane = ReactiveCache.getSwimlane({
_id: paramSwimlaneId,
@ -626,7 +626,7 @@ if (Meteor.isServer) {
try {
const paramBoardId = req.params.boardId;
const paramSwimlaneId = req.params.swimlaneId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
Swimlanes.remove({ _id: paramSwimlaneId, boardId: paramBoardId });
JsonRoutes.sendResult(res, {
code: 200,