CVE-2025-67438
Description
A Stored Cross-Site Scripting (XSS) vulnerability in Sync-in Server before 1.9.3 allows an authenticated attacker to execute arbitrary JavaScript in a victim's browser. By uploading a crafted SVG file containing a malicious payload, an attacker can access and exfiltrate sensitive information, including the user's session cookies.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A stored XSS vulnerability in Sync-in Server before 1.9.3 lets authenticated attackers upload malicious SVG files to execute arbitrary JavaScript and steal session cookies.
Vulnerability
Overview
A stored cross-site scripting (XSS) vulnerability exists in Sync-in Server versions prior to 1.9.3. The root cause is that the server's file serving endpoint did not force a Content-Disposition: attachment header for all file types, allowing SVG files uploaded by authenticated users to be rendered inline rather than downloaded [1][2]. When an SVG file contains embedded JavaScript, it executes in the victim's browser context under the Sync-in domain.
Exploitation
Prerequisites
An attacker must be authenticated attacker must upload a crafted SVG file containing a malicious JavaScript payload [1]. The attacker then tricks a victim (directly or indirectly) causes a victim to access the raw SVG file URL. No additional authentication is required for the victim; they need only browse to the vulnerable endpoint [3]. The fix in commit a6276d0 modifies the SendFile class to remove the asAttachment flag and introduce a helper function makeContentDispositionAttachment, ensuring that files are served with a Content-Disposition: attachment header unless explicitly configured otherwise [2].
Impact
Successful exploitation allows arbitrary JavaScript execution in the victim's browser, enabling the attacker to exfiltrate session cookies, CSRF tokens, and other sensitive data within the Sync-in domain [3]. This can lead to account takeover and privilege escalation. The vulnerability is rated as high severity.
Mitigation
The vulnerability is patched in Sync-in Server version 1.9.3. Users are strongly advised to upgrade immediately [1][2][3].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@sync-in/servernpm | < 1.9.3 | 1.9.3 |
Affected products
2- Sync-in/Sync-in Serverdescription
Patches
1a6276d067725fix(backend:security): prevent stored XSS by serving files with `Content-Disposition: attachment` to avoid arbitrary JavaScript execution in the browser
5 files changed · +14 −10
backend/src/applications/files/services/files-manager.service.ts+2 −2 modified@@ -80,8 +80,8 @@ export class FilesManager { public readonly filesLockManager: FilesLockManager ) {} - sendFileFromSpace(space: SpaceEnv, asAttachment = false, downloadName = ''): SendFile { - return new SendFile(space.realPath, asAttachment, downloadName) + sendFileFromSpace(space: SpaceEnv, downloadName = ''): SendFile { + return new SendFile(space.realPath, downloadName) } async saveStream(
backend/src/applications/files/services/files-tasks-manager.service.ts+1 −1 modified@@ -104,7 +104,7 @@ export class FilesTasksManager { throw new HttpException('Not applicable', HttpStatus.BAD_REQUEST) } const rPath = path.join(user.tasksPath, task.name) - const sendFile = new SendFile(rPath, true) + const sendFile = new SendFile(rPath) try { await sendFile.checks() } catch (e) {
backend/src/applications/files/utils/send-file.ts+7 −5 modified@@ -11,12 +11,16 @@ import { DEFAULT_HIGH_WATER_MARK } from '../constants/files' import { FileError } from '../models/file-error' import { fileName, isPathExists, isPathIsDir, isPathIsReadable } from './files' +export function makeContentDispositionAttachment(fileName: string) { + const downloadName = fileName.normalize('NFD').replace(/[\u0300-\u036f]/g, '') + return `attachment; filename="${downloadName}";filename*=UTF-8''${fileName}` +} + export class SendFile { private fileName: string constructor( private readonly filePath: string, - private readonly asAttachment = true, private readonly downloadName = '', private readonly sendOptions: SendOptions = { acceptRanges: true, @@ -53,10 +57,8 @@ export class SendFile { if (sendResult.metadata['path'] === undefined) { throw new FileError(HttpStatus.BAD_REQUEST, 'Location not found') } - if (this.asAttachment) { - const downloadName = this.fileName.normalize('NFD').replace(/[\u0300-\u036f]/g, '') - sendResult.headers['content-disposition'] = `attachment; filename="${downloadName}";filename*=UTF-8''${this.fileName}` - } + // Force file to be downloaded as an attachment + sendResult.headers['content-disposition'] = makeContentDispositionAttachment(this.fileName) res.headers(sendResult.headers) res.status(sendResult.statusCode) // sendStream.once('stream', () => console.log(`Sending: ${this.fileName}`))
backend/src/applications/links/services/links-manager.service.ts+1 −1 modified@@ -62,7 +62,7 @@ export class LinksManager { this.logger.log(`${this.linkAccess.name} - *${user.login}* (${user.id}) downloading ${spaceLink.share.name}`) this.incrementLinkNbAccess(link) const spaceEnv: SpaceEnv = await this.spaceEnvFromLink(user, spaceLink) - const sendFile: SendFile = this.filesManager.sendFileFromSpace(spaceEnv, true, spaceLink.share.name) + const sendFile: SendFile = this.filesManager.sendFileFromSpace(spaceEnv, spaceLink.share.name) try { await sendFile.checks() return await sendFile.stream(req, res)
backend/src/applications/users/users.controller.ts+3 −1 modified@@ -9,6 +9,7 @@ import { createReadStream } from 'fs' import { LoginResponseDto } from '../../authentication/dto/login-response.dto' import { AuthTwoFaGuardWithoutPassword } from '../../authentication/guards/auth-two-fa-guard' import { FastifyAuthenticatedRequest } from '../../authentication/interfaces/auth-request.interface' +import { makeContentDispositionAttachment } from '../files/utils/send-file' import { USERS_ROUTE } from './constants/routes' import { USER_PERMISSION, USER_ROLE } from './constants/user' import { UserHavePermission } from './decorators/permissions.decorator' @@ -27,6 +28,7 @@ import { Member } from './interfaces/member.interface' import { UserAppPassword } from './interfaces/user-secrets.interface' import { UserModel } from './models/user.model' import { UsersManager } from './services/users-manager.service' +import { USER_AVATAR_FILE_NAME } from './utils/avatar' @Controller(USERS_ROUTE.BASE) @UseGuards(UserRolesGuard) @@ -90,7 +92,7 @@ export class UsersController { async avatar(@GetUser() user: UserModel, @Param('login') login: 'me' | string): Promise<StreamableFile> { const isMe: boolean = login === 'me' const [path, mime] = await this.usersManager.getAvatar(isMe ? user.login : login, false, isMe && user.role <= USER_ROLE.USER) - return new StreamableFile(createReadStream(path), { type: mime }) + return new StreamableFile(createReadStream(path), { type: mime, disposition: makeContentDispositionAttachment(USER_AVATAR_FILE_NAME) }) } @Put(`${USERS_ROUTE.ME}/${USERS_ROUTE.AVATAR}`)
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.