{"id":13411955,"url":"https://github.com/markets/invisible_captcha","last_synced_at":"2025-05-12T13:24:30.025Z","repository":{"id":3515497,"uuid":"4573607","full_name":"markets/invisible_captcha","owner":"markets","description":"🍯 Unobtrusive and flexible spam protection for Rails apps","archived":false,"fork":false,"pushed_at":"2025-01-30T10:52:30.000Z","size":240,"stargazers_count":1198,"open_issues_count":0,"forks_count":66,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-23T16:08:39.204Z","etag":null,"topics":["anti-spam","captcha","honeypot","honeypot-field","rails","security","spam-detection"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/invisible_captcha","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/markets.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},"funding":{"github":["markets"]}},"created_at":"2012-06-06T14:30:32.000Z","updated_at":"2025-04-14T14:20:20.000Z","dependencies_parsed_at":"2024-02-20T22:24:48.903Z","dependency_job_id":"e52ddf8e-a092-465e-9cb6-d2fd35b369d6","html_url":"https://github.com/markets/invisible_captcha","commit_stats":{"total_commits":218,"total_committers":24,"mean_commits":9.083333333333334,"dds":"0.22935779816513757","last_synced_commit":"b0093de90621f8362223f28ce51fe57e9f067d7d"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markets%2Finvisible_captcha","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markets%2Finvisible_captcha/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markets%2Finvisible_captcha/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markets%2Finvisible_captcha/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/markets","download_url":"https://codeload.github.com/markets/invisible_captcha/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253745828,"owners_count":21957450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["anti-spam","captcha","honeypot","honeypot-field","rails","security","spam-detection"],"created_at":"2024-07-30T20:01:19.191Z","updated_at":"2025-05-12T13:24:30.005Z","avatar_url":"https://github.com/markets.png","language":"Ruby","readme":"# Invisible Captcha\n\n[![Gem](https://img.shields.io/gem/v/invisible_captcha.svg?style=flat-square)](https://rubygems.org/gems/invisible_captcha)\n[![Build Status](https://github.com/markets/invisible_captcha/workflows/CI/badge.svg)](https://github.com/markets/invisible_captcha/actions)\n[![codecov](https://codecov.io/gh/markets/invisible_captcha/branch/master/graph/badge.svg?token=nADSa6rbhM)](https://codecov.io/gh/markets/invisible_captcha)\n\n\u003e Complete and flexible spam protection solution for Rails applications.\n\nInvisible Captcha provides different techniques to protect your application against spambots.\n\nThe main protection is a solution based on the `honeypot` principle, which provides a better user experience since there are no extra steps for real users, only for the bots.\n\nEssentially, the strategy consists on adding an input field :honey_pot: into the form that:\n\n- shouldn't be visible by the real users\n- should be left empty by the real users\n- will most likely be filled by spam bots\n\nIt also comes with:\n- a time-sensitive :hourglass: form submission\n- an IP based :mag: spinner validation\n\n## Installation\n\nInvisible Captcha is tested against Rails `\u003e= 5.2` and Ruby `\u003e= 2.7`.\n\nAdd this line to your Gemfile and then execute `bundle install`:\n\n```ruby\ngem 'invisible_captcha'\n```\n\n## Usage\n\nView code:\n\n```erb\n\u003c%= form_for(@topic) do |f| %\u003e\n  \u003c%= f.invisible_captcha :subtitle %\u003e\n  \u003c!-- or --\u003e\n  \u003c%= invisible_captcha :subtitle, :topic %\u003e\n\u003c% end %\u003e\n```\n\nController code:\n\n```ruby\nclass TopicsController \u003c ApplicationController\n  invisible_captcha only: [:create, :update], honeypot: :subtitle\nend\n```\n\nThis method will act as a `before_action` that triggers when spam is detected (honeypot field has some value). By default, it responds with no content (only headers: `head(200)`). This is a good default, since the bot will surely read the response code and will think that it has achieved to submit the form properly. But, anyway, you can define your own callback by passing a method to the `on_spam` option:\n\n```ruby\nclass TopicsController \u003c ApplicationController\n  invisible_captcha only: [:create, :update], on_spam: :your_spam_callback_method\n\n  private\n\n  def your_spam_callback_method\n    redirect_to root_path\n  end\nend\n```\n\nYou should _not_ name your method `on_spam`, as this will collide with an internal method of the same name.\n\nNote that it is not mandatory to specify a `honeypot` attribute (neither in the view nor in the controller). In this case, the engine will take a random field from `InvisibleCaptcha.honeypots`. So, if you're integrating it following this path, in your form:\n\n```erb\n\u003c%= form_tag(new_contact_path) do |f| %\u003e\n  \u003c%= invisible_captcha %\u003e\n\u003c% end %\u003e\n```\n\nIn your controller:\n\n```\ninvisible_captcha only: [:new_contact]\n```\n\n`invisible_captcha` sends all messages to `flash[:error]`. For messages to appear on your pages, add `\u003c%= flash[:error] %\u003e` to `app/views/layouts/application.html.erb` (somewhere near the top of your `\u003cbody\u003e` element):\n\n```erb\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003ctitle\u003eYet another Rails app\u003c/title\u003e\n  \u003c%= stylesheet_link_tag    \"application\", media: \"all\" %\u003e\n  \u003c%= javascript_include_tag \"application\" %\u003e\n  \u003c%= csrf_meta_tags %\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n  \u003c%= flash[:error] %\u003e\n  \u003c%= yield %\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nYou can place `\u003c%= flash[:error] %\u003e` next to `:alert` and `:notice` message types, if you have them in your `app/views/layouts/application.html.erb`.\n\n**NOTE:** This gem relies on data set by the backend, so in order to properly work, your forms should be rendered by Rails. Forms generated via JavaScript are not going to work well.\n\n## Options and customization\n\nThis section contains a description of all plugin options and customizations.\n\n### Plugin options:\n\nYou can customize:\n\n- `sentence_for_humans`: text for real users if input field was visible. By default, it uses I18n (see below).\n- `honeypots`: collection of default honeypots. Used by the view helper, called with no args, to generate a random honeypot field name. By default, a random collection is already generated. As the random collection is stored in memory, it will not work if you are running multiple Rails instances behind a load balancer (see [Multiple Rails instances](#multiple-rails-instances)). Beware that Chrome may ignore `autocomplete=\"off\"`. Thus, consider not to use field names, which would be autocompleted, like for example `name`, `country`.\n- `visual_honeypots`: make honeypots visible, also useful to test/debug your implementation.\n- `timestamp_threshold`: fastest time (in seconds) to expect a human to submit the form (see [original article by Yoav Aner](https://blog.gingerlime.com/2012/simple-detection-of-comment-spam-in-rails/) outlining the idea). By default, 4 seconds. **NOTE:** It's recommended to deactivate the autocomplete feature to avoid false positives (`autocomplete=\"off\"`).\n- `timestamp_enabled`: option to disable the time threshold check at application level. Could be useful, for example, on some testing scenarios. By default, true.\n- `timestamp_error_message`: flash error message thrown when form submitted quicker than the `timestamp_threshold` value. It uses I18n by default.\n- `injectable_styles`: if enabled, you should call anywhere in your layout the following helper `\u003c%= invisible_captcha_styles %\u003e`. This allows you to inject styles, for example, in `\u003chead\u003e`. False by default, styles are injected inline with the honeypot.\n- `spinner_enabled`: option to disable the IP spinner validation. By default, true.\n- `secret`: customize the secret key to encode some internal values. By default, it reads the environment variable `ENV['INVISIBLE_CAPTCHA_SECRET']` and fallbacks to random value. Be careful, if you are running multiple Rails instances behind a load balancer, use always the same value via the environment variable.\n\nTo change these defaults, add the following to an initializer (recommended `config/initializers/invisible_captcha.rb`):\n\n```ruby\nInvisibleCaptcha.setup do |config|\n  # config.honeypots           \u003c\u003c ['more', 'fake', 'attribute', 'names']\n  # config.visual_honeypots    = false\n  # config.timestamp_threshold = 2\n  # config.timestamp_enabled   = true\n  # config.injectable_styles   = false\n  # config.spinner_enabled     = true\n\n  # Leave these unset if you want to use I18n (see below)\n  # config.sentence_for_humans     = 'If you are a human, ignore this field'\n  # config.timestamp_error_message = 'Sorry, that was too quick! Please resubmit.'\nend\n```\n\n#### Multiple Rails instances\n\nIf you have multiple Rails instances running behind a load balancer, you have to share the same honeypots collection between the instances.\n\nEither use a fixed collection or share them between the instances using `Rails.cache`:\n\n```ruby\nInvisibleCaptcha.setup do |config|\n  config.honeypots = Rails.cache.fetch('invisible_captcha_honeypots') do\n    (1..20).map { InvisibleCaptcha.generate_random_honeypot }\n  end\nend\n```\n\nBe careful also with the `secret` setting. Since it will be stored in-memory, if you are running this setup, the best idea is to provide the environment variable (`ENV['INVISIBLE_CAPTCHA_SECRET']`) from your infrastructure.\n\n### Controller method options:\n\nThe `invisible_captcha` method accepts some options:\n\n- `only`: apply to given controller actions.\n- `except`: exclude to given controller actions.\n- `honeypot`: name of custom honeypot.\n- `scope`: name of scope, ie: 'topic[subtitle]' -\u003e 'topic' is the scope. By default, it's inferred from the `controller_name`.\n- `on_spam`: custom callback to be called on spam detection.\n- `timestamp_enabled`: enable/disable this technique at action level.\n- `on_timestamp_spam`: custom callback to be called when form submitted too quickly. The default action redirects to `:back` printing a warning in `flash[:error]`.\n- `timestamp_threshold`: custom threshold per controller/action. Overrides the global value for `InvisibleCaptcha.timestamp_threshold`.\n- `prepend`: the spam detection will run in a `prepend_before_action` if `prepend: true`, otherwise will run in a `before_action`.\n\n### View helpers options:\n\nUsing the view/form helper you can override some defaults for the given instance. Actually, it allows to change:\n\n- `sentence_for_humans`\n\n```erb\n\u003c%= form_for(@topic) do |f| %\u003e\n  \u003c%= f.invisible_captcha :subtitle, sentence_for_humans: \"hey! leave this input empty!\" %\u003e\n\u003c% end %\u003e\n```\n- `visual_honeypots`\n\n```erb\n\u003c%= form_for(@topic) do |f| %\u003e\n  \u003c%= f.invisible_captcha :subtitle, visual_honeypots: true %\u003e\n\u003c% end %\u003e\n```\n\nYou can also pass html options to the input:\n\n```erb\n\u003c%= invisible_captcha :subtitle, :topic, id: \"your_id\", class: \"your_class\" %\u003e\n```\n\n### Spam detection notifications\n\nIn addition to the `on_spam` controller callback, you can use the [Active Support Instrumentation API](https://guides.rubyonrails.org/active_support_instrumentation.html) to set up a global event handler that fires whenever spam is detected. This is useful for advanced logging, background processing, etc.\n\nTo set up a global event handler, [subscribe](https://guides.rubyonrails.org/active_support_instrumentation.html#subscribing-to-an-event) to the `invisible_captcha.spam_detected` event in an initializer:\n\n```ruby\n# config/initializers/invisible_captcha.rb\n\nActiveSupport::Notifications.subscribe('invisible_captcha.spam_detected') do |*args, data|\n  AwesomeLogger.warn(data[:message], data) # Log to an external logging service.\n  SpamRequest.create(data)                 # Record the blocked request in your database.\nend\n```\n\nThe `data` passed to the subscriber is hash containing information about the request that was detected as spam. For example:\n\n```ruby\n{\n  message: \"Honeypot param 'subtitle' was present.\",\n  remote_ip: '127.0.0.1',\n  user_agent: 'Chrome 77',\n  controller: 'users',\n  action: 'create',\n  url: 'http://example.com/users',\n  params: {\n    topic: { subtitle: 'foo' },\n    controller: 'users',\n    action: 'create'\n  }\n}\n```\n\n**NOTE:** `params` will be filtered according to your `Rails.application.config.filter_parameters` configuration, making them (probably) safe for logging. But always double-check that you're not inadvertently logging sensitive form data, like passwords and credit cards.\n\n### Content Security Policy\n\nIf you're using a Content Security Policy (CSP) in your Rails app, you will need to generate a nonce on the server, and pass `nonce: true` attribute to the view helper. Uncomment the following lines in your `config/initializers/content_security_policy.rb` file:\n\n```ruby\n# Be sure to restart your server when you modify this file.\n\n# If you are using UJS then enable automatic nonce generation\nRails.application.config.content_security_policy_nonce_generator = -\u003e request { SecureRandom.base64(16) }\n\n# Set the nonce only to specific directives\nRails.application.config.content_security_policy_nonce_directives = %w(style-src)\n```\nNote that if you are already generating nonce for scripts, you'd have to include `script-src` to `content_security_policy_nonce_directives` as well:\n\n```ruby\nRails.application.config.content_security_policy_nonce_directives = %w(script-src style-src)\n```\n\nAnd in your view helper, you need to pass `nonce: true` to the `invisible_captcha` helper:\n\n```erb\n\u003c%= invisible_captcha nonce: true %\u003e\n```\n\n**NOTE:** Content Security Policy can break your site! If you already run a website with third-party scripts, styles, images, and fonts, it is highly recommended to enable CSP in report-only mode and observe warnings as they appear. Learn more at MDN:\n\n* https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP\n* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n\n### I18n\n\n`invisible_captcha` tries to use I18n when it's available by default. The keys it looks for are the following:\n\n```yaml\nen:\n  invisible_captcha:\n    sentence_for_humans: \"If you are human, ignore this field\"\n    timestamp_error_message: \"Sorry, that was too quick! Please resubmit.\"\n```\n\nYou can override the English ones in your i18n config files as well as add new ones for other locales.\n\nIf you intend to use I18n with `invisible_captcha`, you _must not_ set `sentence_for_humans` or `timestamp_error_message` to strings in the setup phase.\n\n## Testing your controllers\n\nIf you're encountering unexpected behaviour while testing controllers that use the `invisible_captcha` action filter, you may want to disable timestamp check for the test environment. Add the following snippet to the `config/initializers/invisible_captcha.rb` file:\n\n```ruby\n# Be sure to restart your server when you modify this file.\n\nInvisibleCaptcha.setup do |config|\n  config.timestamp_enabled = !Rails.env.test?\nend\n```\n\nAnother option is to wait for the timestamp check to be valid:\n\n```ruby\n# Maybe inside a before block\nInvisibleCaptcha.init!\nInvisibleCaptcha.timestamp_threshold = 1\n\n# Before testing your controller action\nsleep InvisibleCaptcha.timestamp_threshold\n```\n\nIf you're using the \"random honeypot\" approach, you may want to set a known honeypot:\n\n```ruby\nconfig.honeypots = ['my_honeypot_field'] if Rails.env.test?\n```\n\nAnd for the \"spinner validation\" check, you may want to disable it:\n\n```ruby\nconfig.spinner_enabled = !Rails.env.test?\n```\n\nOr alternativelly, you should send a valid spinner value along your requests:\n\n```ruby\n# RSpec example\nsession[:invisible_captcha_spinner] = '32ab649161f9f6faeeb323746de1a25d'\npost :create,  params: { topic: { title: 'foo' }, spinner: '32ab649161f9f6faeeb323746de1a25d' }\n```\n\n## Contribute\n\nAny kind of idea, feedback or bug report are welcome! Open an [issue](https://github.com/markets/invisible_captcha/issues) or send a [pull request](https://github.com/markets/invisible_captcha/pulls).\n\n## Development\n\nClone/fork this repository, start to hack on it and send a pull request.\n\nRun the test suite:\n\n```\n$ bundle exec rspec\n```\n\nRun the test suite against all supported versions:\n\n```\n$ bundle exec appraisal install\n$ bundle exec appraisal rspec\n```\n\nRun specs against specific version:\n\n```\n$ bundle exec appraisal rails-7.0 rspec\n```\n\n### Demo\n\nStart a sample Rails app ([source code](spec/dummy)) with `InvisibleCaptcha` integrated:\n\n```\n$ bundle exec rake web # PORT=4000 (default: 3000)\n```\n\n## License\n\nCopyright (c) Marc Anguera. Invisible Captcha is released under the [MIT](LICENSE) License.\n","funding_links":["https://github.com/sponsors/markets"],"categories":["Ruby","Captchas and anti-spam"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkets%2Finvisible_captcha","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkets%2Finvisible_captcha","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkets%2Finvisible_captcha/lists"}