CVE-2023-38510 (Kotlin, published 2023-07-27T19:15Z)
Description: value: Tolgee is an open-source localization platform. Starting in version 3.14.0 and prior to version 3.23.1, when a request is made using an API key, the backend fails to verify the permission scopes associated with the key, effectively bypassing permission checks entirely for some endpoints. It's important to note that this vulnerability only affects projects that have inadvertently exposed their API keys on the internet. Projects that have kept their API keys secure are not impacted. This issue is fixed in version 3.23.1.
Severity: nan (CVSS v3 base score: 8.1)
Weakness: CWE-862 \u2014 Missing Authorization
The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
Repository: https://github.com/tolgee/tolgee-platform
Files changed:
backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/V2ImportController.kt
backend/app/src/main/kotlin/io/tolgee/websocket/WebSocketConfig.kt
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2ImportController/V2ImportControllerApplicationTest.kt
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/v2KeyController/KeyControllerCreationTest.kt
backend/app/src/test/kotlin/io/tolgee/service/TelemetryServiceTest.kt
backend/app/src/test/resources/application.yaml
backend/data/src/main/kotlin/io/tolgee/repository/dataImport/ImportLanguageRepository.kt
backend/data/src/main/kotlin/io/tolgee/service/dataImport/StoredDataImporter.kt
backend/data/src/main/kotlin/io/tolgee/service/security/SecurityService.kt
ee/backend/tests/src/test/resources/application.yaml
Commit message: fix: Disable importing with any project permission (#1818)



Vulnerable code:
@Tag(name = "Import")
              securityService.checkProjectPermission(projectId = projectId, Scope.TRANSLATIONS_VIEW, user)
import io.tolgee.testing.AuthorizedControllerTest
class V2ImportControllerApplicationTest : AuthorizedControllerTest() {
        node("[0]").isEqualTo("""["keys.edit"]""")
  @ProjectApiKeyAuthTestMethod(scopes = [Scope.KEYS_CREATE])
    "tolgee.telemetry.enabled=true"
    telemetryProperties.enabled = true
      securityService.checkProjectPermission(import.project.id, Scope.KEYS_EDIT)
  fun checkProjectPermission(projectId: Long, requiredScopes: Scope, userAccountDto: UserAccountDto) {
    checkProjectPermissionOr(projectId, listOf(requiredScopes), userAccountDto)
  }

    checkProjectPermissionOr(listOf(requiredScopes), apiKey)
  private fun checkProjectPermissionOr(requiredScopes: List<Scope>, apiKey: ApiKey) {
    this.checkApiKeyScopesOr(requiredScopes, apiKey)
  fun checkProjectPermissionOr(
    requiredScopes: Collection<Scope>,
    checkProjectPermissionOr(projectId, requiredScopes, allowedScopes)
  fun checkProjectPermissionOr(
    projectId: Long,
    requiredScopes: Collection<Scope>,
    if (!allowedScopes.any { requiredScopes.contains(it) }) {
        listOf(requiredScopes.map { it.value }) as List<Serializable>
    val apiKey = activeApiKey ?: return checkProjectPermission(projectId, requiredPermission, activeUser)
  fun checkProjectPermissionOr(projectId: Long, requiredPermissions: Collection<Scope>) {
    checkProjectPermissionOr(projectId, requiredPermissions, activeUser)
  }

  fun checkApiKeyScopesOr(scopes: Collection<Scope>, apiKey: ApiKey) {
      if (!expandedScopes.any { it in apiKey.scopesEnum }) {

Fixed code:
import io.tolgee.model.enums.Scope
import io.tolgee.service.security.SecurityService
@Tag(
  name = "Import",
  description = "These endpoints handle multi-step data import"
)
  private val securityService: SecurityService
    checkBaseImportPermissions()
    checkBaseImportPermissions()
  private fun checkBaseImportPermissions() {
    securityService.checkProjectPermission(projectHolder.project.id, Scope.TRANSLATIONS_VIEW)
  }

    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
    checkBaseImportPermissions()
              securityService.checkProjectPermissionNoApiKey(projectId = projectId, Scope.TRANSLATIONS_VIEW, user)
import io.tolgee.ProjectAuthControllerTest
import io.tolgee.model.enums.Scope
import io.tolgee.testing.annotations.ProjectApiKeyAuthTestMethod
class V2ImportControllerApplicationTest : ProjectAuthControllerTest("/v2/projects/") {
        node("[0]").isEqualTo(""""keys.create"""")
  @Test
  @ProjectApiKeyAuthTestMethod(scopes = [Scope.TRANSLATIONS_VIEW])
  fun `it checks permissions with API key (view only)`() {
    val testData = ImportTestData()
    testData.importBuilder.data.importFiles[0].data.importKeys.removeIf { it.self == testData.newLongKey }
    val resolveFrench = testData.addFrenchTranslations()
    resolveFrench()

    testDataService.saveTestData(testData.root)
    projectSupplier = { testData.project }
    userAccount = testData.userAccount

    val path = "import/apply?forceMode=OVERRIDE"
    performProjectAuthPut(path, null).andIsForbidden
  }

import io.tolgee.fixtures.andIsForbidden
  @ProjectApiKeyAuthTestMethod(scopes = [Scope.KEYS_CREATE, Scope.TRANSLATIONS_EDIT])
  @ProjectApiKeyAuthTestMethod(scopes = [Scope.KEYS_CREATE])
  @Test
  fun `create key with translations require translate permissions`() {
    performProjectAuthPost("keys", CreateKeyDto(name = "super_key", translations = mapOf("en" to "", "de" to "")))
      .andIsForbidden
  }

    "tolgee.telemetry.enabled=false"
    telemetryProperties.enabled = false
    telemetryProperties.enabled = true
    server: http://localhost:8080

  @Query(
    """
      select distinct il.existingLanguage.id 
        from ImportLanguage il 
        join il.file if 
        where if.import.id = :importId 
          and il.existingLanguage.id is not null
    """
  )
  fun findAssignedExistingLanguageIds(importId: Long): List<Long>
      securityService.checkProjectPermission(import.project.id, Scope.KEYS_CREATE)
    checkProjectPermission(listOf(requiredScopes), apiKey)
  private fun checkProjectPermission(requiredScopes: List<Scope>, apiKey: ApiKey) {
    this.checkApiKeyScopes(requiredScopes, apiKey)
  fun checkProjectPermissionNoApiKey(
    requiredScope: Scope,
    checkPermission(requiredScope, allowedScopes)
  private fun checkPermission(
    requiredScope: Scope,
    if (!allowedScopes.contains(requiredScope)) {
        listOf(requiredScope.value) as List<Serializable>
    val apiKey = activeApiKey ?: return checkProjectPermissionNoApiKey(projectId, requiredPermission, activeUser)
  fun checkApiKeyScopes(scopes: Collection<Scope>, apiKey: ApiKey) {
      val hasRequiredPermission = scopes.all { expandedScopes.contains(it) }
      if (!hasRequiredPermission) {
    server: http://localhost:8080