Repository navigation
feat: add security hardening and fix vetted backlog issues - #56
Merged
nejdetkadir merged 1 commit intoAug 25, 2026
Merged
Conversation
Resolves SEC-2/4/5/7 and KI-1/2/3/4/6/7/9/10/11 from docs/analysis, plus GH-51 (token values in logs): - refresh token rotation + family-revocation reuse detection behind refresh_token.rotation_enabled (default off) - unique DB indexes on token secrets with RecordNotUnique retry - paranoid mode and error_response.verbose_account_state flags to stop account enumeration and lockable-state leakage - filter token secrets from request logs and Token#inspect - refresh with unknown token now returns invalid_refresh_token (400); new invalid_login error for non-email authentication keys - emit failed_attempts alongside the deprecated failed_attemps typo - consolidate memoized current_devise_api_refresh_token helper - drop rubocop-disable scar tissue, RBS stub; standardize Time.current - backfill CHANGELOG, update contractual docs, 100% line+branch coverage Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nejdetkadir
deleted the
feature/add-refresh-rotation-and-security-hardening
branch
August 25, 2026 16:15
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.
Summary
docs/analysis/): refresh-token rotation with reuse detection (SEC-2), DB-enforced token uniqueness (SEC-4), account-enumeration hardening (SEC-5/SEC-7), and token-secret log filtering (Filtering logging of the access_token #51)invalid_refresh_tokennow used byrefresh, newinvalid_loginerror for non-email authentication keys, canonicalfailed_attemptskey emitted alongside the deprecatedfailed_attempstypo, duplicate refresh-token helper consolidatedChanges
lib/devise/api/configuration.rb— new settings:refresh_token.rotation_enabled(defaultfalse),paranoid(defaultfalse),error_response.verbose_account_state(defaulttrue)app/controllers/devise/api/tokens_controller.rb— refresh reuse detection (revokes the whole token family viaToken#revoke_family!when a rotated/revoked refresh token is replayed); unknown refresh token now returnsinvalid_refresh_token(400) instead ofinvalid_token(401); actions rewritten around sharedrender_token_response/render_error_responsehelpers (allrubocop:disablecomments removed)app/services/.../tokens_service/refresh.rb— with rotation on, mints the new token and revokes the presented one in a single transactionapp/services/.../tokens_service/create.rb— rescuesActiveRecord::RecordNotUniqueand retries with fresh tokens (3 attempts)app/services/.../resource_owner_service/authenticate.rb— unknown account returnsinvalid_emailonly when:emailis an authentication key,invalid_loginotherwise, and genericinvalid_authenticationin paranoid modelib/devise/api/responses/error_response.rb—failed_attempts+ deprecatedfailed_attemps; lockable/confirmable blocks gated byverbose_account_state/paranoid; status mapping via constantslib/devise/api/token.rb—revoke!,revoke_family!,filter_attributesredaction of token secrets,Time.currentlib/devise/api/rails/engine.rb— initializer addsaccess_token/refresh_token/previous_refresh_tokento the host app'sfilter_parameterslib/devise/api/generators/templates/migration.rb.erb— unique indexes onaccess_token/refresh_tokenrefresh_token_rotation_spec.rb,paranoid_mode_spec.rb,engine_spec.rb; extended token/config/error-response/service/generator/helpers specsArchitecture Impact
remove_index+add_index unique: trueon both token columns). Dummy app migration + schema updated accordingly.POST /<scope>/tokens/refreshwith an unknown refresh token now respondsinvalid_refresh_token(400) instead ofinvalid_token(401) — changelogged.failed_attemptskey), or default token flow; rotation/paranoid/verbose flags default to current behavior.Test Plan
bundle exec rake: 321 examples, 0 failures, 100% line + branch coverage, rubocop clean)Related
#inspect; SQL-log caveat documented in README)🤖 Generated with Claude Code