Skip to content

Skip absent optional steps when composing structured error messages#2751

Open
ericproulx wants to merge 1 commit into
masterfrom
fix/2748-structured-message-leak
Open

Skip absent optional steps when composing structured error messages#2751
ericproulx wants to merge 1 commit into
masterfrom
fix/2748-structured-message-leak

Conversation

@ericproulx
Copy link
Copy Markdown
Contributor

Fixes #2748.

Problem

Grape::Exceptions::Base#compose_message loops over every entry in MESSAGE_STEPS (problem, summary, resolution) and translates each one individually. For an optional step a message doesn't define — e.g. summary on invalid_message_bodyGrape::Util::Translation#translate falls back to returning the scoped key path as a string. That string is non-blank, so it passes the detail.present? guard and leaks verbatim into the rendered message:

Grape::Exceptions::InvalidMessageBody.new('application/json').message
# Problem:
#   message body does not match declared format
# Summary:
#   grape.errors.messages.invalid_message_body.summary   <- leaked i18n key
# Resolution:
#   when specifying application/json as content-type, ...

Fix

Gate each step on whether the already-resolved message hash defines it (short_message.key?(step.to_sym)). The resolved hash already reflects the :en fallback, so:

  • absent optional steps (e.g. summary) are skipped instead of leaking the key path;
  • :en locale fallback for present steps is preserved (verified under a :de locale with no override — still renders Problem + Resolution from :en, no leak);
  • the intentional key-path behaviour for a fully unresolvable top-level message (spec/grape/exceptions/base_spec.rb "returns the scoped translation key as a string") is untouched — that path returns before the Hash branch;
  • translate itself is unchanged, so validators and ValidationErrors are unaffected.

Tests

Added specs in spec/grape/exceptions/base_spec.rb: the leak is gone for invalid_message_body, while missing_vendor_option (which legitimately defines summary) still renders all three steps. spec/grape/exceptions/ is green (96 examples, 0 failures).

Reported by @dcasillano.

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 31, 2026

Danger Report

No issues found.

View run

@ericproulx ericproulx force-pushed the fix/2748-structured-message-leak branch from 305b403 to 8eb2ac7 Compare May 31, 2026 17:29
@ericproulx ericproulx requested a review from dblock May 31, 2026 18:14
`Grape::Exceptions::Base#compose_message` looped over every entry in
`MESSAGE_STEPS` (problem, summary, resolution) and translated each one
individually. For an optional step a message does not define — e.g.
`summary` on `invalid_message_body` — `Grape::Util::Translation#translate`
falls back to returning the scoped key path as a string. That string is
non-blank, so it passed the `detail.present?` guard and leaked verbatim:

    Summary:
      grape.errors.messages.invalid_message_body.summary

Gate each step on whether the already-resolved message hash defines it.
The resolved hash reflects the :en fallback, so locale fallback for
present steps is preserved, and the existing key-path behaviour for a
fully-unresolvable top-level message is untouched.

Fixes #2748.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing summary translation leaks raw i18n key into error messages (3.2.1)

1 participant