Preserve ampersands in legacy user detail sanitization

What does this MR do and why?

Saving an & in the bluesky, discord, linkedin, mastodon, orcid, twitter, or github field now stores the value as entered instead of HTML-encoded. These values stay valid once the validate_sanitizable_user_details feature flag is enabled.

Problem

Currently on master, saving one of those seven fields with an & in the value stores an HTML-encoded copy instead of the value the user entered. Any user who puts an & in one of these fields is affected. For example, a LinkedIn path of in/a&b is stored as in/a&b.

The flag-off sanitizer runs Sanitize.clean on those seven fields. Sanitize.clean always encodes &. Once the validate_sanitizable_user_details feature flag is on, the Sanitizable validator rejects any value containing an escaped HTML entity. Every row that already carries an encoded & then fails validation the next time it saves. For example, a user who once saved in/a&b as their LinkedIn value can no longer update their bio, because the stored in/a&b fails validation.

The feature flag is off by default, so nothing fails yet. Without this change, the affected rows keep accumulating until it is enabled.

Review notes

  • Why not reuse Sanitizable.sanitize, the flag-on method? It returns the input unchanged when the value contains any HTML entity. It relies on the validator to reject it. The flag-off path has no such validator. Reusing it would let raw tags like <script>alert(1)</script>&amp; through unstripped.
  • Does decoding &amp; weaken sanitization? No. The gsub only turns &amp; into &, so it cannot produce a raw < or >. Double-encoded input like &amp;lt; still comes out single-encoded as &lt;. The location, company, and website_url fields already work this way since the website_url ampersand fix.
  • What about rows already saved with an encoded &amp;? Left alone here. A separate MR will clean them up with a batched background migration.

🛠️ with ❤️ at Siemens

References

Screenshots or screen recordings

Nothing visual changed.

How to set up and validate locally

  1. In a Rails console, disable the feature flag and save a field with an ampersand.

    Feature.disable(:validate_sanitizable_user_details)
    user = User.first
    user.user_detail.update!(linkedin: 'in/a&b')
    user.user_detail.reload.linkedin
    Field Before this MR After this MR
    linkedin in/a&amp;b in/a&b
  2. Enable the flag and confirm the saved row now validates.

    Feature.enable(:validate_sanitizable_user_details)
    user.user_detail.valid?

    Returns true.

  3. Confirm tags are still stripped.

    user.user_detail.update!(twitter: '<b>name</b>')
    user.user_detail.reload.twitter

    Returns name.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Gerardo Navarro

Merge request reports

Loading
Loading