Add Redactable - #9347
Draft
garethrees wants to merge 13 commits into
Draft
Conversation
garethrees
force-pushed
the
8823-redactable-2
branch
from
July 3, 2026 15:46
4ec9a88 to
08ffbb4
Compare
* Allow records to set redactable attributes
* Redact redactable attributes by default
* Add an #unredacted method to get the unredacted version of a redacted
attribute
* Allow classes to define an `apply_masks_to_{{attr}}` method for custom
mask behaviour for given records. This is most likely to be necessary
when applying masks to a non text/plain content type
* Reduce complexity of fallback * Redactions are now handled by OutgoingMessage so don't need to pass the censor rules through
Issues with title/slug interaction; title isn't redacted at the moment anyway
Removed redactable from InfoRequest for now
The original body method has important self-healing logic: if the attachment record is stale (hexdigest doesn't match), it calls FoiAttachment::MaskJob to reparse the raw email and rebuild the attachment. But since redactable prepends a module that intercepts every call to #body, apply_masks_to_body now runs instead of the real method — and it re-derives content from mail_attributes directly, with no rebuild/retry logic at all. So when an attachment's hexdigest is stale, mail_attributes raises FoiAttachment::MissingError immediately. That bubbles up to IncomingMessage#get_main_body_text_internal, which does rescue FoiAttachment::MissingError; retry — expecting the rebuild to have already happened inside body. It never does, so it retries forever, re-parsing the raw email each iteration, until memory/disk usage explodes and Docker OOM-kills the container. apply_masks_to_body now sources from unredacted.body instead of unmasked_body, so it routes through the real body method's rebuild logic instead of bypassing it.
When attributes are genuinely nil we still want to return nil rather than an empty string, even though we're now passing these attributes through the redactable pipeline.
Need to explicitly check the unredacted email
garethrees
force-pushed
the
8823-redactable-2
branch
from
August 5, 2026 13:04
cc3dbcd to
05bd2b2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relevant issue(s)
CensorRulepolymorphic & refactor #9302What does this do?
Adds explicit interfaces for getting redacted/unredacted versions of content
Why was this needed?
Implementation notes
Here's how this will work:
Notes to reviewer
While this does force explicit unredacted permission, it still requires us to remember to add attributes to
redactable. There's an alternative version of this where we by default assume attributes will hold PII unless explicitly told they won't, but that feels like it would get a bit too annoying?At the moment this applies both censor rules and text masks. We may want to split these up so that we can do e.g. (
record.censored.unmasked.foo).Have you updated the changelog? If this is not necessary, put square brackets around this: [skip changelog]