[Security] Redacted attachment filenames exposed in downloads

17 views
Skip to first unread message

Gareth Rees

unread,
Mar 17, 2026, 12:34:24 PM (8 days ago) Mar 17
to Alaveteli Dev
# [Security] Redacted attachment filenames exposed in downloads

We've discovered and patched an issue where attachment filenames redacted by censor rules can be exposed via download options.

We've released a fix in 0.46.2.0 [1] and 0.45.4.0 [2]. If you are running an older version of Alaveteli please see the pull request and patch your Alaveteli as appropriate [3].

# Summary and impact

The censor rule correctly redacts the filename on the public request page. However, when the file is downloaded, the saved file retains the original unredacted file path. Where filenames contained personal data (for example requesters’ names), this could reveal identifying information that should have been redacted.

This problem exists via two actions:

1. Downloading a zip file of a batch request in version 0.38.0.0+  
2. Downloading (rather than "viewing") an attachment in version 0.46.0.0+

The problem does not affect downloading a zip of an individual request.

# Fix

We've updated code to use  `FoiAttachment#display_filename` rather than the raw unredacted `FoiAttachment#filename` value. See pull request #9163 [3].

# Checking affected content

You can run the following script to check whether you have attachments with a redacted filename:

```
affected_requests = if CensorRule.global.exists?
  InfoRequest.all
else
  by_request = CensorRule.where.not(info_request_id: nil).pluck(:info_request_id)
  by_user    = CensorRule.where.not(user_id: nil).pluck(:user_id)
  by_body    = CensorRule.where.not(public_body_id: nil).pluck(:public_body_id)

  InfoRequest.where(id: by_request)
             .or(InfoRequest.where(user_id: by_user))
             .or(InfoRequest.where(public_body_id: by_body))
end

attachments =
  affected_requests.
  includes(:censor_rules, public_body: :censor_rules, user: :censor_rules).
  find_each.flat_map do |request|
    request.foi_attachments.find_each.select do |a|
      a.filename != a.redacted_filename
    end
  end

# Print a list of attachment IDs where the filename is redacted
puts attachments.map(&:id).join("\n")

# Print a CSV containing additional diagnostic information
str = CSV.generate do |csv|
  csv << %w[
    attachment_id,
    admin_url,
    public_url,
    received_on
    filename,
    redacted_filename,
    batch_id
  ]

  attachments.each do |attachment|
    request_slug = attachment.incoming_message.info_request.url_title
    received_on = attachment.incoming_message.created_at.strftime('%Y-%m-%d')
    batch_id = attachment.incoming_message.info_request.info_request_batch_id

    csv << [
      attachment.id,
      "/admin/attachments/#{attachment.id}/edit",
      "/request/#{request_slug}#attachment-#{attachment.id}",
      received_on,
      attachment.filename,
      attachment.display_filename,
      batch_id
    ]
  end
end

puts str
```

You can map this information to information in the admin UI and/or server logs to assess impact and severity in your specific install. Let us know if you need any assistance in diagnosing this issue.

# Affected versions

Exposing unredacted attachment filenames via downloading a zip file of a batch request was introduced in version 0.38.0.0.

Exposing unredacted attachment filenames via downloading an attachment in version 0.46.0.0.

# Fixed versions

We've released a fix in 0.46.2.0 [1] and 0.45.4.0 [2]. If you are running an older version of Alaveteli please see the pull request and patch your Alaveteli as appropriate [3].

As ever, reach out if you have any questions.

Best,

Gareth

[1] https://github.com/mysociety/alaveteli/releases/tag/0.46.2.0
[2] https://github.com/mysociety/alaveteli/releases/tag/0.45.4.0
[3] https://github.com/mysociety/alaveteli/pull/9163
Reply all
Reply to author
Forward
0 new messages