Skip to content

fix(security): restrict translation tooling to contributors and admins - #2019

Merged
Asi0Flammeus merged 2 commits into
fix/onlyoffice-callbackfrom
fix/translation-access
Oct 25, 2026
Merged

Asi0Flammeus merged 2 commits into
fix/onlyoffice-callbackfrom
fix/translation-access

Conversation

@Asi0Flammeus

Copy link
Copy Markdown
Collaborator

Stacked on #2016 (OnlyOffice callback): it also puts the role guard on the pptx-callback-token route added there. Merge #2016 first; this PR then retargets to dev.

Vulnerability

Translation tooling open to every account. The REST routes of the translation tool were guarded by expressAuthMiddleware only, which checks that a session exists. Any student account could:

  • POST /api/upload-translation-files, /start-translation/:uploadId, /retry-translation/:courseId: delete the source files of a course under contribute/<courseId>/…, replace them, and start paid ConvertAPI / Language Toolkit jobs;
  • POST /api/translation-audio/generate: generate text-to-speech audio of any text with a professor's voice, stored in S3;
  • create proofread copies, generate PNGs, read the job lists and statuses.

The matching tRPC procedures already use contributorProcedure / adminProcedure, and the academy's translation panel is admin-only.

Arbitrary S3 read through the PNG viewer. GET /api/png-viewer/translated/…/:fileName/:slideNumber takes fileName as the slide's ppt_resource_path, URL-decoded, and builds the S3 key with two replace calls. A value without /pptx/ and .pptx came out unchanged, so any key of the bucket (CVs, submitted assignments, certificates…) was streamed to the caller.

Fix

  • createExpressRoleMiddleware(role) in apps/api/src/middlewares/auth.ts, built on canAccess like the tRPC procedures: 401 without a session, 403 when the role is not enough. New Forbidden error.
  • Admin role: upload, start, retry, upload/job status and job list routes (translation-uploads.ts).
  • Contributor role (contributors, admins, superadmins): translation-audio/*, the editor routes of translation-downloads.ts that required a session (pptx-forcesave, pptx-create-proofread-copy, generate-png, pptx-token, audio/…, pptx/…, pptx-callback-token) and the four png-viewer routes. Routes fetched by the OnlyOffice server, which has no session, are unchanged.
  • buildTranslatedPngKey / buildTranslatedProofreadPngKey refuse (400) any path that is not contribute/…/pptx/<name>.pptx with no empty, . or .. segment.

Tests

apps/api gets a mocha setup like shared and service-user.

  • apps/api/src/routers/rest/translation-access.test.ts (express on 127.0.0.1:0, fake database/S3, outbound fetch recorded). On dev every one fails, with the status shown; here all pass:
    • refuses translation uploads from a student (202 == 403)
    • refuses translation retries from a contributor (202 == 403)
    • refuses text-to-speech generation from a student (200 == 403)
    • refuses proofread copies from a student (400 == 403)
    • refuses the PNG viewer to a student (404 == 403)
    • does not let the translated PNG viewer read objects outside slides (cvs/profile-1 was read)
  • apps/api/src/utils/png-path.test.ts:
    • refuse paths that are not slide presentations: fails on dev (Missing expected exception: cvs/1234), passes here;
    • build the keys of slide PNGs: passes on both.
  • translation-callback.test.ts from fix(security): authenticate the OnlyOffice save callback #2016 now runs with a contributor session and still passes.

turbo run type-check --filter=@blms/api passes and biome check is clean on the changed files.

Behaviour changes and deploy notes

  • No new environment variable.
  • Translators need the contributor role (or admin) to use the slide editor, audio generation and PNG viewer. Accounts that used them with another role (professor, community, student) now get 403. Check the roles of active translators before deploying.
  • Upload, start/retry and job monitoring of translations need the admin role, like the academy's translation panel.
  • Not covered here: a contributor can still act on any course and language. Checking the contributor's assignment (createCheckUserTranslationAssignment) on each route is a follow-up.

The REST translation routes (uploads, start/retry, jobs, text-to-speech,
proofread copies, PNG generation, editor downloads, PNG viewer) only
required a session, so any student could delete and replace a course's
source files, start paid translations or generate audio with a
professor's voice. Upload and job routes now require the admin role,
the slide editing routes the contributor role (createExpressRoleMiddleware:
401 without session, 403 when the role is not enough).

The translated PNG viewer turned the client-supplied ppt_resource_path
into an S3 key, so any object of the bucket could be read. The key
builders now refuse anything but contribute/.../pptx/<name>.pptx.
@Asi0Flammeus
Asi0Flammeus merged commit 7be6981 into fix/onlyoffice-callback Oct 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant