VYPR
Moderate severityNVD Advisory· Published Feb 20, 2026· Updated Feb 23, 2026

CVE-2025-67438

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.

PackageAffected versionsPatched versions
@sync-in/servernpm
< 1.9.31.9.3

Affected products

2

Patches

1
a6276d067725

fix(backend:security): prevent stored XSS by serving files with `Content-Disposition: attachment` to avoid arbitrary JavaScript execution in the browser

https://github.com/Sync-in/serverjohavenDec 6, 2025via ghsa
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

5

News mentions

0

No linked articles in our index yet.