flucksite/otori
GitHub: flucksite/otori
Otori 是一个为任意 Rack based Ruby 应用提供隐形蜜罐、时间检测和 JS 行为信号三层反垃圾表单防护的轻量库。
Stars: 1 | Forks: 0
# Otori
[](https://codeberg.org/fluck/otori/actions?workflow=ci.yml)
[](https://codeberg.org/fluck/otori/tags)
为 Hanami、Sinatra、Roda、Rails 以及任何其他基于 Rack 的 Ruby 应用提供隐形蜜罐垃圾防护。你可以将其视为一个与框架无关的
[invisible_captcha](https://github.com/markets/invisible_captcha),它拥有
一流的 Hanami 适配器,并额外增加了一层基于 JavaScript 的输入检测。
_Otori_ (囮) 在日语中意为“诱饵”,历史上指狩猎时用来吸引其他鸟类的鸟。
这是 [Crystal lucky_honeypot
shard](https://codeberg.org/fluck/lucky_honeypot) 的 Ruby 配套版本。它将三种经典技术结合在一个 gem 中:
1. **隐形字段**。机器人会填写所有字段,包括那些用 CSS 隐藏的字段。
2. **时间检查**。机器人会瞬间提交表单,而人类需要更多时间。
3. **输入信号**。机器人通常不会触发鼠标、触摸、滚动、键盘或聚焦事件。
当前两项检查中有任何一项失败时,提交将被静默拒绝。
机器人会以为提交成功并继续下一步。第三项检查可用于在选定的_人类评分_阈值下拒绝或标记提交。
## 对比
### otori
- 框架:Hanami、Sinatra、Roda、Rails,任何 Rack 应用
- 隐藏字段 + 时间检查 + JS 输入信号
- 活跃维护(2026 年新推出)
### [invisible_captcha](https://github.com/markets/invisible_captcha)
- 框架:仅限 Rails
- 隐藏字段 + 时间检查
- 积极维护中
### [rack-honeypot](https://github.com/sunlightlabs/rack-honeypot)
- 框架:任何 Rack 应用
- 仅限隐藏字段
- 自 2016 年起停止维护
如果你使用的是 Rails 且不需要 JS 信号层,`invisible_captcha` 是经过更多实战考验的选择。Otori 则为其他所有情况而生。
## 安装说明
将此内容添加到你的 Gemfile 中:
```
gem "otori"
```
然后运行 `bundle install`。需要 Ruby 3.2 或更高版本。
## 快速入门
该 gem 提供了一个与框架无关的核心,以及一个可选的 Hanami 适配器。核心 API 包含三个调用:
```
Otori.field("user[website]", session: request.session)
Otori.signals_field
Otori.caught?("user[website]", params: request.params, session: request.session)
```
`field` 会渲染隐形输入框,并在 session 中存储加载时间戳。
`signals_field` 会渲染一个隐藏输入框以及用于追踪人类输入的 JavaScript。
`caught?` 会检查提交的表单,并在请求看起来像机器人时返回 `true`。
字段名称使用标准的 HTML 方括号表示法,因此 `"user[website]"` 在提交后会位于 `params[:user][:website]` 下。
像 `"note"` 这样的扁平名称也同样适用。选择最适合周围表单的名称即可,蜜罐在真实字段旁边看起来越可信,效果就越好。
## 框架集成
### Hanami
Hanami 适配器需要显式加载,以保持基础 gem 无额外依赖:
```
require "otori/hanami"
```
将这些辅助方法混入你的视图中:
```
# app/views/helpers.rb
module MyApp
module Views
module Helpers
include Otori::Hanami::Helpers
end
end
end
```
在表单模板中:
```
<%= honeypot_field("user[website]") %>
<%= honeypot_signals %>
```
这些辅助方法通过 `String#html_safe` 将其输出标记为安全,Hanami View 开箱即用地提供了此功能,因此 HTML 在流经 ERB 时不会被转义。
使用 `honeypot` DSL 方法保护接收操作:
```
# app/actions/sign_ups/create.rb
module MyApp
module Actions
module SignUps
class Create < MyApp::Action
include Otori::Hanami::Action
honeypot "user[website]"
def handle(request, response)
# ...
end
end
end
end
end
```
当蜜罐被触发时,默认情况下该操作将以 `204 No Content` 终止。要自定义响应,请传递一个 block:
```
honeypot "user[website]" do |_request, response|
response.flash[:info] = "Moving on..."
response.redirect_to "/", status: 303
end
```
支持多个蜜罐,每个蜜罐都有自己的时间和处理程序:
```
honeypot "user[website]", wait: 5
honeypot "note" do |_req, _res|
halt 422
end
```
要根据输入信号评分采取行动,请在 `handle` 中对其进行评估:
```
def handle(request, response)
rating = Otori.signals_rating(request.params.to_h)
halt 204 if rating < 0.4
# ...
end
```
### Rack (Sinatra、Roda、纯 Rack)
没有需要额外引入的适配器,核心 API 就足够了。在 Sinatra 应用中:
```
require "otori"
enable :sessions
get "/sign_up" do
erb :sign_up
end
post "/sign_up" do
halt 204 if Otori.caught?(
"user[website]",
params: params,
session: session
)
# ...
end
```
在视图中:
```
<%= Otori.field("user[website]", session: session) %>
<%= Otori.signals_field %>
```
### Rails
当你只需要隐藏字段和时间检查时,Rails 已经有了很好的 [invisible_captcha](https://github.com/markets/invisible_captcha)
解决方案。如果你想在 Rails 中使用输入信号评分,请使用此 gem。
```
# app/helpers/application_helper.rb
module ApplicationHelper
def honeypot_field(name, **attrs)
Otori.field(name, session: session, **attrs).html_safe
end
def honeypot_signals(**attrs)
Otori.signals_field(**attrs).html_safe
end
end
```
```
# app/controllers/sign_ups_controller.rb
class SignUpsController < ApplicationController
before_action :check_honeypot, only: :create
def create
# ...
end
private
def check_honeypot
return unless Otori.caught?(
"user[website]",
params: params.to_unsafe_h,
session: session
)
head :no_content
end
end
```
## 配置
```
Otori.configure do |c|
# Required delay (in seconds) between page load and form submission.
c.default_delay = 2.0
# Disables the submission delay entirely. Useful in tests.
c.disable_delay = false
# Name of the hidden input that carries the signals payload.
c.signals_input_name = "honeypot_signals"
end
```
## 隐形字段
默认情况下,该字段会使用内联 `style` 属性进行渲染,该属性会将其移出视觉流,且不会破坏辅助工具的可用性。传递你自己的 `class`(或 `style`)以取消默认样式,转而使用你的 CSS:
```
Otori.field("user[website]", session: session, class: "visually-hidden")
```
带有下划线的属性键会转换为连字符,因此 `data_foo: "bar"` 会渲染为 `data-foo="bar"`。任何其他属性对都将原样传递。
## 检测输入信号
`signals_field` 会渲染一个隐藏输入框以及一个小的内联 `