Exempt the app's configured CSRF param, not just authenticity_token - #84
Merged
Merged
Conversation
FORM_KEYS hardcoded "authenticity_token" for the top-level unknown: :error exemption, but config.action_controller.request_forgery_protection_token lets an app rename that parameter. Renaming it reproduced the exact bug FORM_KEYS was added to fix: unknown: :error tripped on every ordinary browser form submission, just under the app's own CSRF key instead of the default one. permittable_check_unknown now also subtracts the live controller's own request_forgery_protection_token (read fresh per request, since the configured name can vary by controller and isn't knowable at class-load time) alongside the existing FORM_KEYS/ROUTING_KEYS exemption. A plain params duck or a standalone Contract has no such method and keeps the FORM_KEYS default unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
VSN2015
pushed a commit
that referenced
this pull request
Sep 29, 2026
Bumps version.rb, adds the 0.10.0 CHANGELOG section, and includes the regenerated Gemfile.lock — CI runs bundler in frozen mode, so a version bump without the lockfile fails the tag build. Eleven PRs since 0.9.0 (#74-#84): numeric strings must be spelled canonically (no underscore separators or surrounding whitespace), an array default: runs its sub-fields' transform: so an omitted field and an explicitly-sent identical value agree, a CSRF param renamed via request_forgery_protection_token no longer trips unknown: :error, a sensitive: field's default:/example: are omitted from the exported schema, message: is deep-frozen like the other authored values, the generator guards an enum accessor the model lacks and no longer swallows non-ActiveRecord errors, the error-response and scalar schema constants are handed out as deep copies, the sensitive-parameter sink list is read under its mutex, and the Rails dev dependencies move to 8.1.4. Minor rather than patch: no new surface, but a client sending "1_8" or " 99 " for a numeric field now gets 422 instead of a number, and a sub-field transform: now runs over an array default: at class load. See CHANGELOG.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
The bug
FORM_KEYS(lib/permittable.rb, around line 232) hardcodes the literal"authenticity_token"as the CSRF parameter name to exempt from the top-levelunknown: :errorcheck:That's Rails' default CSRF parameter name, but
config.action_controller.request_forgery_protection_tokenlets an app rename it. An app that does tripsunknown: :erroron every ordinary browser form submission — reproducing exactly the bugFORM_KEYSwas added to fix in the first place (per the comment right above it), just under the app's own key instead of the default one:A normal form POST then carries
csrf_tokeninstead ofauthenticity_token,FORM_KEYSdoesn't recognize it, and the request fails with{ param: "csrf_token", code: "unknown" }even though nothing is wrong with it.Since the configured name can differ per controller and isn't knowable at class-load time (Rails may not have finished initializing when this file first loads), a frozen constant computed once can't hold it — it has to be read live, per request.
The fix
permittable_check_unknownnow also subtracts the live controller's ownrequest_forgery_protection_token(fromActionController::RequestForgeryProtection, included byActionController::Base) from the top-level exemption, alongside the existingFORM_KEYS/ROUTING_KEYSconstant andpermittable_request_supplied_keys:A plain params duck or a standalone
Contracthas norequest_forgery_protection_tokenmethod, sopermittable_configured_csrf_keyreturnsnilthere and behavior is unchanged — they still only get theFORM_KEYSdefault's"authenticity_token".FORM_KEYS,ROUTING_KEYS, andMONITOR_DROPPED_KEYSare untouched: monitor mode's raw pass-through still only drops routing keys, exactly as before.Verification
spec/permittable_spec.rb), confirmed to fail (422 instead of 200) before the implementation change and pass afterbundle exec rspec— 852 examples, 0 failuresbundle exec rubocop lib/permittable.rb spec/permittable_spec.rb— no offenses🤖 Generated with Claude Code