{"id":19745159,"url":"https://github.com/castle/castle_devise","last_synced_at":"2026-02-27T16:04:25.490Z","repository":{"id":40260639,"uuid":"375990363","full_name":"castle/castle_devise","owner":"castle","description":"Out-of-the-box fraud prevention for Devise model powered by Castle","archived":false,"fork":false,"pushed_at":"2025-11-12T12:41:05.000Z","size":176,"stargazers_count":5,"open_issues_count":1,"forks_count":2,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-11-30T05:36:40.710Z","etag":null,"topics":["castle","criticality-1","devise"],"latest_commit_sha":null,"homepage":"https://castle.io","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/castle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-06-11T10:37:49.000Z","updated_at":"2025-10-13T13:24:41.000Z","dependencies_parsed_at":"2024-07-12T08:32:40.912Z","dependency_job_id":"ac4e96af-94f1-40bd-bd43-67f93d493afd","html_url":"https://github.com/castle/castle_devise","commit_stats":{"total_commits":41,"total_committers":6,"mean_commits":6.833333333333333,"dds":"0.36585365853658536","last_synced_commit":"c75d22c805b6c37c86b56c8532294c4bf77c9804"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/castle/castle_devise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/castle%2Fcastle_devise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/castle%2Fcastle_devise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/castle%2Fcastle_devise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/castle%2Fcastle_devise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/castle","download_url":"https://codeload.github.com/castle/castle_devise/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/castle%2Fcastle_devise/sbom","scorecard":{"id":267563,"data":{"date":"2025-08-11","repo":{"name":"github.com/castle/castle_devise","commit":"05cff237fcece5f17717e18213d10aa60235bf98"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.9,"checks":[{"name":"Code-Review","score":10,"reason":"all changesets reviewed","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":3,"reason":"4 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/lint.yml:1","Warn: no topLevel permission defined: .github/workflows/specs.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/castle/castle_devise/lint.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/lint.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/castle/castle_devise/lint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/specs.yml:47: update your workflow using https://app.stepsecurity.io/secureworkflow/castle/castle_devise/specs.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/specs.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/castle/castle_devise/specs.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/specs.yml:65: update your workflow using https://app.stepsecurity.io/secureworkflow/castle/castle_devise/specs.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   3 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":8,"reason":"2 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-353f-x4gh-cqq8","Warn: Project is vulnerable to: GHSA-mqcp-p2hv-vw6x"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T12:29:39.920Z","repository_id":40260639,"created_at":"2025-08-17T12:29:39.920Z","updated_at":"2025-08-17T12:29:39.920Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29903617,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T14:46:13.553Z","status":"ssl_error","status_checked_at":"2026-02-27T14:46:10.522Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["castle","criticality-1","devise"],"created_at":"2024-11-12T02:04:28.858Z","updated_at":"2026-02-27T16:04:25.474Z","avatar_url":"https://github.com/castle.png","language":"Ruby","readme":"[![Gem Version](https://badge.fury.io/rb/castle_devise.svg)](https://badge.fury.io/rb/castle_devise)\n\n**Disclaimer:** CastleDevise is currently in beta. There might be some upcoming breaking changes to the gem before we stabilize the API.\n\n---\n\n# CastleDevise\n\nCastleDevise is a [Devise](https://github.com/heartcombo/devise) plugin that integrates [Castle](https://castle.io).\n\nIt currently provides the following features:\n- preventing bots from registration attacks using Castle's [Filter API](https://docs.castle.io/v1/reference/api-reference/#filter)\n- preventing ATO attacks using Castle's [Risk API](https://docs.castle.io/v1/reference/api-reference/#risk)\n- blocks attempts to update passwords for high-risk logged-in users\n- logs attempts of password reset flows so that you can see them on the Castle dashboard\n\nIf you want to learn about all capabilities of Castle, please take a look at [our documentation](https://docs.castle.io/).\n\n## Installation\n\nInclude `castle_devise` in your Gemfile:\n\n```ruby\ngem 'castle_devise'\n```\n\nCreate `config/initializers/castle_devise.rb` and fill in your API secret and APP_ID from the [Castle Dashboard](https://dashboard.castle.io/settings/general)\n\n```ruby\nCastleDevise.configure do |config|\n  config.api_secret = ENV.fetch('CASTLE_API_SECRET')\n  config.app_id = ENV.fetch('CASTLE_APP_ID')\n\n  # When monitoring mode is enabled, CastleDevise sends\n  # requests to Castle but it doesn't act on the \"deny\" verdicts.\n  #\n  # This is useful when you want to check out how Castle scores\n  # your traffic without blocking any of your users.\n  #\n  # Once you are ready to use Castle as your security provider,\n  # you can set monitoring_mode to false.\n  config.monitoring_mode = true\nend\n```\n\nAdd `:castle_protectable` Devise module to your User model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  devise :database_authenticatable, :registerable,\n         :recoverable, :rememberable, :validatable,\n         :castle_protectable # \u003c--- add this\nend\n```\n\nAdd an additional translation to your `config/locales/devise.en.yml`:\n\n```yml\nen:\n  devise:\n    registrations:\n      blocked_by_castle: \"Account cannot be created at this moment. Please try again later.\"\n```\n\n(See [devise.en.yml in our specs](spec/dummy_app/config/locales/devise.en.yml#L40))\n\n#### Further steps if you're not using Webpacker\n\nInclude Castle's c.js script in the head section of your layout:\n\n```ruby\n\u003c%= castle_javascript_tag %\u003e\n```\n\nAdd the following tag to the the `\u003cform\u003e` tag in both `devise/registrations/new.html.erb` and `devise/sessions/new.html.erb` (if you haven't generated them yet, run `rails generate devise:views`):\n\n```ruby\n\u003c%= form_for @user,  html: { onsubmit: castle_on_form_submit } do |f| %\u003e\n  …\n\u003c% end %\u003e\n```\n\nYou're set! Now verify that everything works by logging in to your application as any user. You should be able to see that User on the [Castle Users Page](https://dashboard.castle.io/users)\n\n\n#### Further steps if you're using Webpacker\n\nAdd `@castleio/castle-js` to your package.json file:\n\n```\nyarn add @castleio/castle-js\n```\n\nconfigure castle in your application pack:\n\n```javascript\nimport * as Castle from '@castleio/castle-js'\n\nCastle.configure(YOUR_APPLICATION_ID);\n```\n\nfor advanced configuration follow [the readme](https://www.npmjs.com/package/@castleio/castle-js#configuration)\n\n## How-Tos\n\n### Customize the login flow\n\n#### Do something after Castle denies a login\n\nWe aim to provide sensible defaults, which means that when Castle denies a login, your application\nwill behave as if the User has not been authenticated. You might still want to log such an event,\nand you can do this in a Warden hook:\n\n```ruby\nWarden::Manager.before_failure do |env, opts|\n  # The raw Castle response if a request to Castle has been made\n  castle_response = env[\"castle_devise.risk_response\"]\n  # CastleDevise::Context, if a request to Castle has been made\n  castle_context = env[\"castle_devise.risk_context\"]\n\n  if castle_response\u0026.dig(:policy, :action) == \"deny\"\n    # auth failed because Castle denied\n  end\nend\n\n```\n\n#### Implement your own challenge flow or do something after an \"allow\" action\n\nIn your `SessionsController`:\n\n```ruby\nclass SessionsController \u003c Devise::SessionsController\n  def create\n    super do |resource|\n      if castle_challenge?\n        # At this point a User is already authenticated, you might want so sign out:\n        sign_out(resource)\n        # .... write your own MFA flow\n        # You can call #castle_risk_response to access Castle response\n        # see https://docs.castle.io/v1/reference/api-reference/#risk for details\n\n        # Fetch the Device token to use it for user feedback\n        # https://docs.castle.io/v1/tutorials/advanced-features/end-user-feedback\n        device_token = castle_risk_response.dig(:device, :token)\n\n        # You might want to fetch our risk signals as well\n        # https://docs.castle.io/v1/reference/signals/\n        event_signals = castle_risk_response[:signals].keys\n        return\n      end\n\n      # do any other action you'd like to perform after a user has been signed in below\n    end\n  end\nend\n```\n\nPlease note that some Devise extensions might completely override `Devise::SessionsController#create`.\nIn this case, you have to handle everything manually -  `castle_challenge?` should be called after\na call to `warden.authenticate!` has been successful.\n\n#### Do not sent login/registration events\n\nYou can configure CastleDevise not to send login or registration events for a given Devise model:\n\n```ruby\nclass User \u003c ApplicationRecord\n  devise :database_authenticatable, :registerable,\n         :castle_protectable,\n         castle_hooks: {\n           # set it to false to prevent CastleDevise\n           # from sending filter($login)\n           before_registration: true,\n           # set it to false to prevent CastleDevise from\n           # sending risk($login) and log($login, $failed)\n           after_login: true,\n           # set it to false to prevent CastleDevise from\n           # sending log($password_reset_request)\n           after_password_reset_request: true\n         }\nend\n```\n\n#### Intercept request/response\n\nYou can register before- and after- request hooks in CastleDevise.\n\n```ruby\nCastleDevise.configure do |config|\n  # Add custom properties to the request but only when sending\n  #   requests to the Risk endpoint\n  # action - Castle API endpoint (eg. :risk, :filter, :log)\n  # context - CastleDevise::Context\n  # payload - Hash (payload passed to the Castle SDK)\n  config.before_request do |action, context, payload|\n    if action == :risk\n      payload[:properties] = {\n        from_eu: context.resource.ip.from_eu?\n      }\n    end\n  end\n\n  config.before_request do |action, context, payload|\n    # you can register multiple before_request hooks\n  end\n\n  # Intercept the response - enrich your logs with Castle signals\n  config.after_request do |action, context, payload, response|\n    Logging.add_tags(response[:signals].keys)\n  end\nend\n```\n\n## Development\n\n### Setup\n\n```bash\nbundle install\n```\n\n### Running tests\n\nMost of the specs should pass just by running the following command:\n\n```bash\nbundle exec rake\n```\n\nWe also have a few VCR tests that will periodically rebuild the cassettes just to make sure that the integration with Castle API is working.\nFor those, you need to run your specs with a proper Castle API Secret:\n\n```bash\nCASTLE_API_SECRET=your_api_secret bundle exec rake\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcastle%2Fcastle_devise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcastle%2Fcastle_devise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcastle%2Fcastle_devise/lists"}