fix(security): restrict file.replaceFile to admins + team members only
Replace was previously accepted from anyone with a relationship to the project: jury (assignment), mentor (mentorAssignment), or team member. That allowed jurors and mentors to swap a team's submission, with the attacker-supplied bucket+objectKey pointing at any object they had uploaded elsewhere. Now only admins and the team itself (submitter or TeamMember) can replace files. Jurors and mentors remain read-only on submissions. The legitimate UI flow (team-lead replacing files from the applicant dashboard) is unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -517,20 +517,14 @@ export const fileRouter = router({
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const isAdminOrObserver = ['SUPER_ADMIN', 'PROGRAM_ADMIN', 'OBSERVER', 'AWARD_MASTER'].includes(ctx.user.role)
|
||||
// Replace is a write operation on the team's submission. Only admins
|
||||
// and the team itself (submitter or team member) may replace files —
|
||||
// jurors and mentors are read-only on project files even though they
|
||||
// can see them. Observers/Award masters are also read-only.
|
||||
const isAdmin = ctx.user.role === 'SUPER_ADMIN' || ctx.user.role === 'PROGRAM_ADMIN'
|
||||
|
||||
if (!isAdminOrObserver) {
|
||||
// Check user has access to the project (assigned or team member)
|
||||
const [assignment, mentorAssignment, teamMembership] = await Promise.all([
|
||||
ctx.prisma.assignment.findFirst({
|
||||
where: { userId: ctx.user.id, projectId: input.projectId },
|
||||
select: { id: true },
|
||||
}),
|
||||
ctx.prisma.mentorAssignment.findFirst({
|
||||
where: { mentorId: ctx.user.id, projectId: input.projectId },
|
||||
select: { id: true },
|
||||
}),
|
||||
ctx.prisma.project.findFirst({
|
||||
if (!isAdmin) {
|
||||
const teamMembership = await ctx.prisma.project.findFirst({
|
||||
where: {
|
||||
id: input.projectId,
|
||||
OR: [
|
||||
@@ -539,10 +533,9 @@ export const fileRouter = router({
|
||||
],
|
||||
},
|
||||
select: { id: true },
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
if (!assignment && !mentorAssignment && !teamMembership) {
|
||||
if (!teamMembership) {
|
||||
throw new TRPCError({
|
||||
code: 'FORBIDDEN',
|
||||
message: 'You do not have access to replace files for this project',
|
||||
|
||||
Reference in New Issue
Block a user