CVE-2025-65017: Decidim core Decidim's Private Data Exports Can Vulnerability

HIGH
Advisory Identifier: GHSA-3cx6-j9j4-54mp
CWE-703 CWE-200
Published
Advisory Summary

CVE-2025-65017 is a HIGH decidim's private data exports can vulnerability (CWE-703: Improper Check or Handling of Exceptional Conditions) affecting Decidim core libraries in the RubyGems ecosystem. The issue involves decidim's private data exports can lead to data leaks. Remediation is available by upgrading affected packages to version 0.30.4.

CVE-2025-65017 at a Glance

CVE Identifier CVE-2025-65017
GHSA Identifier GHSA-3cx6-j9j4-54mp
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-703: Improper Check or Handling of Exceptional Conditions
Affected Product Decidim core
Package Ecosystem RubyGems
Published Date
Last Updated

How to Fix CVE-2025-65017

To remediate this vulnerability, upgrade all affected Decidim core dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

decidim-core fixed: 0.30.4
Upgrade decidim-core to 0.30.4
decidim fixed: 0.30.4
Upgrade decidim to 0.30.4
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for decidim-core

How to Check if You Are Affected

To determine whether your application is affected by CVE-2025-65017, inspect your project dependencies for Decidim core:

inspect dependencies for decidim-core

Analyze the terminal output to locate any version of Decidim core in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.30.4), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-703: Improper Check or Handling of Exceptional Conditions, where the product does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the product. In Decidim core, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Impact Private data exports can lead to data leaks in cases where the UUID generation causes collisions for the generated UUIDs. The bug was introduced by #13571 and affects Decidim versions 0.30.0 or newer (currently 2025-09-23). This issue was discovered by running the following spec several times in a row, as it can randomly fail due to this bug: ```bash $ cd decidim-core $ for i in {1..10}; do bundle exec rspec spec/jobs/decidim/download_your_data_export_job_spec.rb -e "deletes the" || break ; done ``` Run the spec as many times as needed to hit a UUID that converts to `0` through `.to_i`. The UUID to zero conversion does not cause a security issue but the security issue is demonstrated with the following example. The following code regenerates the issue by assigning a predefined UUID that will generate a collision (example assumes there are already two existing users in the system): ```ruby # Create the ZIP buffers to be stored buffer1 = Zip::OutputStream.write_buffer do |out| out.put_next_entry("admin.txt") out.write "Hello, admin!" end buffer1.rewind buffer2 = Zip::OutputStream.write_buffer do |out| out.put_next_entry("user.txt") out.write "Hello, user!" end buffer2.rewind # Create the private exports with a predefined IDs user1 = Decidim::User.find(1) export = user1.private_exports.build export.id = "0210ae70-482b-4671-b758-35e13e0097a9" export.export_type = "download_your_data" export.file.attach(io: buffer1, filename: "foobar.zip", content_type: "application/zip") export.expires_at = Decidim.download_your_data_expiry_time.from_now export.metadata = {} export.save! user2 = Decidim::User.find(2) export = user2.private_exports.build export.id = "0210d2df-a0c7-40aa-ad97-2dae5083e3b8" export.export_type = "download_your_data" export.file.attach(io: buffer2, filename: "foobar.zip", content_type: "application/zip") export.expires_at = Decidim.download_your_data_expiry_time.from_now export.metadata = {} export.save! ``` Expect to see an error in the situation. Now, login as user with ID 1, go to `/download_your_data`, click "Download file" from the export and expect to see the data that should be attached to user with ID 2. This is an artificially replicated situation with the predefined UUIDs but it can easily happen in real situations. The reason for the test case failure can be replicated in case you change the export ID to `export.id = "e9540f96-9e3d-4abe-8c2a-6c338d85a684"`. This would return `0` through `.to_s` After attaching that ID, you can test if the file is available for the export: ```ruby user.private_exports.last.file.attached? => false user.private_exports.last.file.blob => nil ``` Note that this fails with such UUID as shown in the example and could easily lead to collisions in case the UUID starts with a number. E.g. UUID `"0210ae70-482b-4671-b758-35e13e0097a9"` would convert to `210` through `.to_s`. Therefore, if someone else has a "private" export with the prefixes "00000210", "0000210", "000210", "00210", "0210" or "210", that would cause a collision and the file could be attached to the wrong private export. Theoretical chance of collision (the reality depends on the UUID generation algorithm): - Potential combinations of the UUID first part (8 characters hex): 16^8 - Potentially colliding character combinations (8 numbers characters in the range of 0-9): 10^8 - 10^8 / 16^8 ≈ 2.3% (23 / 1000 users) The root cause is that the class `Decidim::PrivateExport` defines an ActiveStorage relation to `file` and the table `active_storage_attachments` stores the related `record_id` as `bigint` which causes the conversion to happen. ### Workarounds Fully disable the private exports feature until a patch is available.

CVE-2025-65017 Affected Decidim core Versions

2 component(s)
Fixed Versions:
0.30.4
Product / Package Ecosystem Affected Versions Fixed Version
decidim-core
RubyGems
≥ 0.30.0
< 0.30.4
0.30.4
decidim
RubyGems
≥ 0.30.0
< 0.30.4
0.30.4

References

Frequently Asked Questions

What is CVE-2025-65017?
CVE-2025-65017 is a security vulnerability in Decidim core involving Decidim's private data exports can lead to data leaks under CWE-703: Improper Check or Handling of Exceptional Conditions.
Which Decidim core versions are affected?
Affected versions include releases prior to 0.30.4 across affected packages: decidim-core, decidim.
What version fixes CVE-2025-65017?
Upgrading to 0.30.4 (or the latest patched release for your respective branch) resolves the vulnerability.
Is decidim-core affected?
Yes, decidim-core is affected in releases prior to 0.30.4. Upgrade your dependency to 0.30.4 or later.
Is decidim affected?
Yes, decidim is affected in releases prior to 0.30.4. Upgrade your dependency to 0.30.4 or later.
What CWE is associated with CVE-2025-65017?
CVE-2025-65017 is classified under CWE-703: Improper Check or Handling of Exceptional Conditions (The product does not properly anticipate or handle exceptional conditions that rarely occur during normal operation of the product.).
How can I check RubyGems dependencies for CVE-2025-65017?
Run the dependency tree command: 'inspect dependencies for decidim-core'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-3cx6-j9j4-54mp",
  "modified" : "2026-02-08T01:01:36Z",
  "published" : "2026-02-03T17:21:17Z",
  "aliases" : [ "CVE-2025-65017" ],
  "summary" : "Decidim's private data exports can lead to data leaks",
  "details" : "### Impact\nPrivate data exports can lead to data leaks in cases where the UUID generation causes collisions for the generated UUIDs.\n\nThe bug was introduced by #13571 and affects Decidim versions 0.30.0 or newer (currently 2025-09-23).\n\nThis issue  was discovered by running the following spec several times in a row, as it can randomly fail due to this bug:\n\n```bash\n$ cd decidim-core\n$ for i in {1..10}; do bundle exec rspec spec/jobs/decidim/download_your_data_export_job_spec.rb -e \"deletes the\" || break ; done\n```\n\nRun the spec as many times as needed to hit a UUID that converts to `0` through `.to_i`.\n\nThe UUID to zero conversion does not cause a security issue but the security issue is demonstrated with the following example.\n\nThe following code regenerates the issue by assigning a predefined UUID that will generate a collision (example assumes there are already two existing users in the system):\n\n```ruby\n# Create the ZIP buffers to be stored\nbuffer1 = Zip::OutputStream.write_buffer do |out|\n  out.put_next_entry(\"admin.txt\")\n  out.write \"Hello, admin!\"\nend\nbuffer1.rewind\nbuffer2 = Zip::OutputStream.write_buffer do |out|\n  out.put_next_entry(\"user.txt\")\n  out.write \"Hello, user!\"\nend\nbuffer2.rewind\n\n# Create the private exports with a predefined IDs\nuser1 = Decidim::User.find(1)\nexport = user1.private_exports.build\nexport.id = \"0210ae70-482b-4671-b758-35e13e0097a9\"\nexport.export_type = \"download_your_data\"\nexport.file.attach(io: buffer1, filename: \"foobar.zip\", content_type: \"application/zip\")\nexport.expires_at = Decidim.download_your_data_expiry_time.from_now\nexport.metadata = {}\nexport.save!\n\n\nuser2 = Decidim::User.find(2)\nexport = user2.private_exports.build\nexport.id = \"0210d2df-a0c7-40aa-ad97-2dae5083e3b8\"\nexport.export_type = \"download_your_data\"\nexport.file.attach(io: buffer2, filename: \"foobar.zip\", content_type: \"application/zip\")\nexport.expires_at = Decidim.download_your_data_expiry_time.from_now\nexport.metadata = {}\nexport.save!\n```\n\nExpect to see an error in the situation.\n\nNow, login as user with ID 1, go to `/download_your_data`, click \"Download file\" from the export and expect to see the data that should be attached to user with ID 2. This is an artificially replicated situation with the predefined UUIDs but it can easily happen in real situations.\n\nThe reason for the test case failure can be replicated in case you change the export ID to `export.id = \"e9540f96-9e3d-4abe-8c2a-6c338d85a684\"`. This would return `0` through `.to_s`\n\nAfter attaching that ID, you can test if the file is available for the export:\n\n```ruby\nuser.private_exports.last.file.attached?\n=> false\nuser.private_exports.last.file.blob\n=> nil\n```\n\nNote that this fails with such UUID as shown in the example and could easily lead to collisions in case the UUID starts with a number. E.g. UUID `\"0210ae70-482b-4671-b758-35e13e0097a9\"` would convert to `210` through `.to_s`. Therefore, if someone else has a \"private\" export with the prefixes \"00000210\", \"0000210\", \"000210\", \"00210\", \"0210\" or \"210\", that would cause a collision and the file could be attached to the wrong private export.\n\nTheoretical chance of collision (the reality depends on the UUID generation algorithm):\n\n- Potential combinations of the UUID first part (8 characters hex): 16^8\n- Potentially colliding character combinations (8 numbers characters in the range of 0-9): 10^8\n- 10^8 / 16^8 ≈ 2.3% (23 / 1000 users)\n\nThe root cause is that the class `Decidim::PrivateExport` defines an ActiveStorage relation to `file` and the table `active_storage_attachments` stores the related `record_id` as `bigint` which causes the conversion to happen.\n\n### Workarounds\nFully disable the private exports feature until a patch is available.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "RubyGems",
      "name" : "decidim-core"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.30.0"
      }, {
        "fixed" : "0.30.4"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "RubyGems",
      "name" : "decidim"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.30.0"
      }, {
        "fixed" : "0.30.4"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/decidim/decidim/security/advisories/GHSA-3cx6-j9j4-54mp"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2025-65017"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/decidim/decidim/pull/13571"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/decidim/decidim"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/decidim/decidim/releases/tag/v0.30.4"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/decidim/decidim/releases/tag/v0.31.0"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/decidim-core/CVE-2025-65017.yml"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/decidim/CVE-2025-65017.yml"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-200", "CWE-703" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-02-03T17:21:17Z",
    "nvd_published_at" : "2026-02-03T15:16:12Z"
  }
}