@ 比多拉/ 守护
运行中的保护 服务器。 它限制请求, 上限的终点 花费钱(AI),检查 在您选择的路径上实现自动化, 读取您的应用程序的答案 找出凶猛的力量 证明性的东西 扫描和 iD扫描,并告诉比多拉安全 它看到了什么,所以它出现在你的面板. 它需要一个活跃的 Bitora 安全订阅 (见 许可证) (中文(简体) ).
- 快速和本地. 这个决定是在你一毫秒之内做出的.
- 失败开放. 如果比多拉不回答,请求就会通过. 你的网站不靠我们.
- 先观察. 默认模式只报告它会做什么 。 切换到
mode: 'enforce'当你高兴的时候. - 无依取相. 18+节点和边缘运行时间.
从答案中发现的(2.0)
应用程序前的一扇门只看到请求. 这个软件包还可以看到每个答案的状态以及用户是谁,哪个
将失败的登录和好的登录区分开来。 适应者呼叫 guard.observe(req, { status }) 您的应用程序回复后
(Express and Hono为您服务;在"Next.js"中,将处理器包入到 guard.route,因为 proxy.ts 编号 : middleware.ts 永远
看答案 德诺在 bitoraDeno) (中文(简体) ).
| 规则 | 当火 | 默认 |
|---|---|---|
brute_force | 一个账户或一个地址在10分钟内登录10个失败,或一天登录30个/40个(攻击缓慢) | 打开,用于 login.paths |
credential_stuffing | 1个地址在 10 分钟( 整个 24 个账户为 15 个) 8个不同账户失败 | 打开 |
password_spraying | 25个不同的账户在10分钟内在全站一次或两次失败. 无地址 | 打开 |
user_enumeration | 一个地址的5个未知账号(你说: guard.auth.failed({ reason: 'unknown_user' })(中文(简体) ) | 打开 |
scan_probe | 12页不同,一分钟内回答404/405 | 打开 |
forced_browsing | 10个不同路线的用户或地址在一分钟内被拒绝(401/403) | 打开 |
id_sweep | 30个同一路线的不同编号(/api/invoices/:id在一分钟内由一个用户或地址阅读 | 打开 |
trap_hit | 请求 /.env, (中文(简体) ), /.git/, (中文(简体) ), /wp-login.php, (中文(简体) ), /phpmyadmin一个真正运行它的网站 traps.except | 执行时的块 |
csrf_cross_site | a POST/PUT/PATCH/DELETE 中包含 Sec-Fetch-Site: cross-site (豁免: csrf.exempt(中文(简体) ) | 只报告 直至 csrf.block: true |
request_abuse | URL 超过 8192 个字符; 身体结束 limits.maxBodyBytes; 不在的方法 limits.methods | 打开 URL; 其余关闭 |
告诉用户是谁user: (req) => id)和登录发生地(login: { paths: ['/api/login'] }) (中文(简体) ). 发生在你身上的事件
代码 : guard.auth.failed({ req, username }), (中文(简体) ), guard.auth.succeeded(...) 财务报告和审定财务报表 guard.event('price_tampered', { req })。 。 。 .
每一个发现都给地址添加了分数(OWASP CRS分数:临界分数为5分,出错为4分,警告为3分,通知为2分),分数会逐渐分出
(单位:千美元)x·e^(-0.008·t)大约87 s)中的一半,然后通过 S(x)=1/(1+e^(-0.75(x-4))): 从 0.65 到 0
429 与 Retry-After从0.88 a 403 (仅在 mode: 'enforce'在观察中,答案是 wouldHave) (中文(简体) ). 漫长
计数器(每个账户和每天)记录着一个消逝的分数会忘记什么。 从未使用过延迟: 在没有服务器时, 拥有者
给钱,所以慢下来是 429。 。 。 .
您的服务器所剩的 : 规则,一个计数,窗口,点,动作,它是否会阻断,路径
模板 (单位:千美元)/api/invoices/:id),对于你自己的事件,一个名字. 从不记账名、密码、 ID、 机构或参数
值:只计算在内存中,缩小为散列.
盾牌( 内容)
它在读取时对路径、查询、用户代理、裁判员和机体进行分数(在您的身体解析器后进行;
何诺和 guard.route 读取一个JSON或可达64KB的形体。 和 OWASP 核心规则集一样, 每条匹配后加分的规则
5号标出请求 规则:SQL注射(UNION SELECT,引文后的同义词,堆放的查询,时间功能,
schema names),XSS(文字标记,事件处理器, javascript: 、路径倒转和敏感文件、包装(php://, (中文(简体) ),
file://, shell 命令, Log4Shell, NoSQL 运算符($ne, (中文(简体) ), $where污染原型(.)__proto__, (中文(简体) ),
constructor.prototype),到云元数据地址的URL,以及给自己起名的扫描仪(sqlmap, nikto, nuclei...).
缩写、引文或 -- 单凭一个人就毫无意义了: O'Brien密码 it's--fine,或提到
select 财务报告和审定财务报表 from 未标注在1级. shield.paranoia: 2 增加规则 捕捉更多,错的更频繁
(SQL-外观句子,危险标记,私人主机,模板表达式);它的意思是先以观察模式运行.
createGuard({
secret: process.env.BITORA_SECRET,
shield: { paranoia: 1, threshold: 5, block: true,
exclusions: [{ match: '/api/posts', params: ['body.markdown'], rules: ['xss'] }] }, // a rich-text field
})
只有每项规则的ID(sqli.union)和参数的命名(query.q, (中文(简体) ), body.email报告。 从不值钱.
标记的请求会像其他任何发现一样在地址上添加点. 盾牌是深入防御的: 参数化查询
输出脱逃是修复注射的方法。 孤独 admin'-- 不被屏蔽在1级(它的分数低于阈值).
它不连接您的数据库驱动程序, 不读取超过 64 KB 的多段上传或机体, 规则是代码
软件包,而不是可下载的签名规则.
罪恶和流动
guardFetch (只有节点, @bitora/guard/node(中文(简体) ) 是一个 fetch 不能针对自己的网络:
import { guardFetch } from '@bitora/guard/node'
const safeFetch = guardFetch(guard, { allow: ['api.stripe.com', '.example.com'] }) // allow is optional
const res = await safeFetch(urlFromTheUser) // throws BlockedUrlError (reason: private_address, scheme, port, redirect...)
只有 https 在端口80/443, no user:password@,名称由图书馆解决 每个 地址必须是
公有(私有,回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回回
拒绝 IPv6 表格 ; 连接到被选中的地址, 而不是名称( 因此 DNS 重新绑定会得到)
无; 每一个方向调整通过相同的检查和下降 Authorization 和饼干; 超时和大小天花板
(5 MB) (英语). 向Bitora报告一个区块只说明原因. allowPrivate: true 已存在用于地方发展,并转动
保护关闭。 它保护通过它的请求,而不是您代码中的其他HTTP客户端.
guard.isBreached(password) 以 k 匿名方式询问 Pwned 密码: 只有 SHA-1 的前五个字符离开您
服务器。 回返 { breached, count, checked }; 如果服务下降, 则返回 checked: false 而不是失败的注册.
guard.flow(name, { user, ip }) 使用来自 flows 选项(短消息、优惠券、退出):
每个用户、每个地址和整个网站。 它会回答的 { action, reasons, retryAfter }; 在观察模式下 action 留下来
allow 财务报告和审定财务报表 wouldHave 说它会做什么.
报名 现在向Bitora报告(仅说明理由,从未输入地址). 流线数字 : ai.wrap 随后流出
回答并更正使用流报的收费( OpenAI: pass) stream_options: { include_usage: true }· ;
Anthropic 默认报告它; 一个报告什么也没有保留估计的流 .
共用计数器(2.1)
检测数在每个实例的记忆中计数. 对于少数必须汇总的预算 每个 实例 服务器(短信、优惠券、一个AI端点的支出、签名)有一个共享商店和两个电话:
import { createGuard } from "@bitora/guard"
import { bitoraStore, upstashStore, redisStore } from "@bitora/guard/store"
const guard = createGuard({
secret: process.env.BITORA_SECRET,
store: bitoraStore({ secret: process.env.BITORA_SECRET }), // or upstashStore({ url, token }) or redisStore(ioredisClient)
})
const r = await guard.limit("coupon", { key: userId, max: 3, windowSec: 3600 }) // { allowed, count, retryAfter, status }
if (!r.allowed) return res.status(r.status).end()
const s = await guard.decide("signup", { email, ip, ipSource }) // MX of the domain and sign-ups per address and per network
- 仓库.
memoryStore()(缺省:仅此一次),bitoraStore(比托拉保留柜台,没有安装;1 呼叫 Bitora perlimit, (中文(简体) ),upstashStore(REST,在边缘和Cloudflare工作)和redisStore(client)(任何客户端incrby, (中文(简体) ),expire财务报告和审定财务报表ttl:ioredis,节点-redis. 一个计数器是固定的窗口,它从第一次被击中开始. guard.decide(kind, ...)询问 Bitora 为何需要每个实例共享的状态, 或搜索软件包无法进行:signup(电子邮件域名是否接收邮件,MX;每个地址和24或64的签名)和login(每个账户和 地址; 从guard.accountOf,从名字。 比多拉只读取了它的域名并放下了. 无法信任来源的地址不计算 .- 短暂暂停和 失败打开 任何地方:如果商店或比多拉不回答,则请求会通过(
checked: false) (中文(简体) ). 单位 观察模式 答案是allow与wouldHave并报道此事. - 被封禁地址列表现在随其标签被下载(
ETag: 一个不变的列表需要空的304。 。 。 .
它不会做的
- 盾牌是用词法和正则表达法为基础的(不使用自由喷出法),所以一个被弄混了的有效载荷可以通过和奇怪的文本 可以2级出行 它不会取代参数化的查询或输出逃出.
- 柜台在每一个实例的记忆中直播. 在没有服务器的情况下,每个实例本身都算数:限制是每个实例,而不是
全球范围,以及
password_spraying只看到自己的流量. - 无工作证明质疑:如果质疑适用,答案是:
429。 。 。 . - 从浏览器直接到您的数据库(Supabase, Firebase)的任何内容都从未经过这里.
- 它不停止量子 DDoS; 属于您的平台的防火墙 .
- 下一个.js
proxy.ts编号 :middleware.ts单独无法读取答案, 所以使用guard.route{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}那些管事的人才是关键.
Sinks:检查一个值在您代码使用时会做什么
盾从请求的形状猜出. 一个水槽检查一个访客的价值 在你的服务器上做了什么,所以它抓住了 盾牌上几乎没有假阳性.
import { createSinks } from '@bitora/guard/sinks' // Node
const sink = createSinks(guard)
sink.sql(query, [req.query.id]) // SQL you build as text: did a value change the structure of the statement?
sink.path('/srv/uploads', req.query.f) // the path must stay inside its folder
sink.filterValue(req.query.name) // a value you put inside a PostgREST .or('name.eq.' + v)
sink.pick(req.body, ['name', 'email']) // only the fields you allow reach .insert() / .update()
单位 observe 水槽从不扔出:它允许调用并报告水槽的名称(从不包含数值)。 单位 enforce 丢出一个
BlockedSinkError· ; pick 总是放弃额外的字段,这就是固定,并报告他们的名字。 参数化的查询仍然是权利
sQL 的修饰 : sink.sql 用于您仍然把它建为文字的地方.
漏在自己的答案中
bitoraExpress(guard, { leaks: true }) 读取每个文本中的第一个 16 KB 回答您的服务器发送和报告时显示 堆栈跟踪, a
数据库出错, 密钥( 条纹键或 AWS 密钥, 私钥, Supabase 服务角色密钥, 用密码连接字符串) 或调试
页 次。只有泄漏的KIND和路径被报告,从没有文本。 包起来 res.write 财务报告和审定财务报表 res.end (不改变客户端)
这是它默认关闭的原因。 对于其他框架,你自己通过: guard.observe(req, { status, body, contentType })。 。 。 .
学习每条路线得到的
createGuard({ learn: { enabled: true } }) 学习,每条路由,哪一个顶级 JSON 字段到达(名称和类型,永远不值)以及哪种方法
被使用。 一旦它看到足够的, 它报告说 没有人发送字段 (大规模分配尝试的外形: role, (中文(简体) ), is_admin, (中文(简体) ), price页:1
字段更改类型 (突然成为对象的字符串: NoSQL 运算符)和 路径未见的方法它什么也挡不住,它
从来没有从它标出的请求中吸取教训。 在正常交通几天后打开它;一个合法的可选字段看起来很奇怪,直到它是
看见了 它学到的可以写下你的代码: guard.schema.zod() (严格Zod计划)和 guard.schema.openapi()。 。 。 .
阴影规则
在允许拒绝任何人之前 createGuard({ shield: { shadow: ['sqli.union', 'xss'] } })规则(或
整个家族,没有"点")在"阴影模式"中被评价并被报告为"会",但不会添加分数,也不会被屏蔽.
你的人工智能功能在回想
有两件事通过AI的特性留下,没有人打算放弃:你为模型写的指令和任何达到其上下文的秘密. guard.aiOutput 读模型的 回复 在你发送之前:
const canary = await guard.aiOutput.canary() // put it inside your system prompt: `... (internal tag: ${canary})`
const answer = await model.generate(...)
const { text, kinds } = await guard.aiOutput.check(answer) // kinds: 'prompt-leak', 'secret', 'card'
return text // enforce mode: redacted; observe mode: unchanged
金丝雀来自你的钥匙, 所以每个实例 你的服务器知道它 不分享状态。 只有 类型 报告给比多拉, 从来没有答案。 它捕捉到你的迅速(大多数取出攻击产生的)的逐字记录,而不是一个解释,它不能阻止一个模型被说服说出其他有害的东西:它读来是漏出,它不是一个内容过滤器。 不要把秘密放在模型的背景中 首先.
你有多少页面到达片段
软件包计算其看到的浏览器的页面导航( 用获取元数据的请求) sec-fetch-dest: document 财务报告和审定财务报表 sec-fetch-mode: navigate并发送 只有那个号码 没有路径、地址或用户。 比多拉将它与页面查看片段报告进行比较,并显示网站覆盖面的分享. 它只计算通过软件包的请求:有一个 Next.js 匹配器仅限于 /api/* 它没有看到页面,该行说没有足够的数据。 广告阻断器和已禁用 JavaScript 合法地降低股份, 因此读取它与你自己的正常, 而不是100% .
严格的CSP, 先试( 只报告)
guard.headers() 返回已发送的严格策略的正则和标题 只报告数字 : Content-Security-Policy-Report-Only (单位:千美元)script-src 与你的无名氏和 strict-dynamic, (中文(简体) ), object-src 'none', (中文(简体) ), base-uri 'none',用于脚本的可信类型), Integrity-Policy-Report-Only 财务报告和审定财务报表 Reporting-Endpoints。它从不屏蔽任何东西:浏览器只说它会拒绝的东西.
const { nonce, headers } = guard.headers() // in your middleware or layout
for (const [k, v] of Object.entries(headers)) response.headers.set(k, v)
// put the nonce on your own scripts: <script nonce={nonce} ...>
浏览器将报告发送到您自己的网站路径( Name/_bitora/r 默认, createGuard({ csp: { reportPath } })) (中文(简体) ). Next.js, Hono, Deno 和 Express 适配器为您回答它(确保您的中件匹配器包含路径); 另有框架调用 await guard.reports(request) 首先,如果答复不是,则返回答复 null。软件包只保留 指令 编辑 东道主 (或"入行","eval","shink"等单词):从不包含脚本样本,文件名,完整URL或页面的查询字符串. 浏览器扩展被忽略 。 任何人都可以发布到这条道路上,所以一个案例接受每小时200份报告(英语:csp.maxPerHour)和比多拉显示它们是一种可能性,而不是证明. 无执照 guard.headers() 返回无信头.
当比多拉不接电话时,关闭开关
Bitora的软件包要求的一切(被封锁地址列表,每个地址检查, guard.decide,活动批次)
一个断路器。 在连续5次失败后,它停止要求5秒,然后让一个呼叫通过看比多拉是否回来了;
每个失败的探测器将暂停(最多5分钟)增加一倍,一个答案会关闭断开器. 与此同时,交通通过 是什么
等着被派去 你的网站从不等待我们的。 一个共享商店,用于 guard.limit() (Redis, Upstash) 有自己的断路器和半秒切出,所以挂起的商店也无法阻挡请求.
要完全关闭软件包( 没有检查、 限制或发送; 站点表现为没有安装) 设置
BITORA_GUARD_DISABLED=1,或通过 createGuard({ disabled: () => myFlag() }) 在每一个呼叫中读取自己的旗帜. guard.license.status()
然后说 disabled。 。 。 .
许可证和订阅
从3.0开始,这个软件包为一个比多拉安全用户的网站工作. 一开始它要求Bitora使用您网站的 API 密钥获得许可
(单位:千美元)BITORA_SECRET请检查date=中的日期值 (帮助) 校对:Soup 执照有七天,所以很短
比多拉的断电不会关掉任何东西.
没有主动订阅、有效的密钥或验证软件包的许可证 什么都不做,让所有的交通通过: 从来没有
拿走你的地盘,它不会发送任何东西。 在你的日志上写了一条线说原因. guard.license.status() 返回时
'active', (中文(简体) ), 'inactive' 或者说 'checking',以及 guard.license.reason() 告诉你为什么它不活动(subscription_required, (中文(简体) ), bad_key, (中文(简体) ),
no_key, (中文(简体) ), unreachable) (中文(简体) ).
在没有服务器的情况下,寒冷实例的第一个请求要等到二分之一半才能获得许可证. 免得这样,把执照给你
上次收到时 : createGuard({ secret, license: process.env.BITORA_LICENSE })。 。 。 .
这些术语是: 自由主义. 2.0至2.1.2版本根据麻省理工学院许可发布.
安装
npm i @bitora/guard
将您站点的 API 密钥( Sitios ~ Clave de la API) 放到环境变量中, 例如 BITORA_SECRET。 。 。 .
下册.jsmiddleware.ts(中文(简体) )
import { createGuard } from '@bitora/guard'
import { bitoraMiddleware } from '@bitora/guard/next'
const guard = createGuard({
secret: process.env.BITORA_SECRET,
mode: 'enforce',
routes: [{ match: '/api/auth', max: 10, windowSec: 60 }],
aiBudget: [{ match: '/api/ai', max: 30, windowSec: 3600 }],
bots: { paths: ['/api/'] },
})
export default bitoraMiddleware(guard)
export const config = { matcher: ['/api/:path*'] }
下册.j. 16proxy.ts(中文(简体) )
import { createGuard } from '@bitora/guard'
import { bitoraProxy } from '@bitora/guard/next'
export const proxy = bitoraProxy(createGuard({ secret: process.env.BITORA_SECRET, mode: 'enforce' }))
export const config = { matcher: ['/api/:path*'] }
Deno 和 Supabase 边缘函数
import { createGuard } from '@bitora/guard'
import { bitoraDeno } from '@bitora/guard/deno'
const guard = createGuard({ secret: Deno.env.get('BITORA_SECRET'), mode: 'enforce', trustProxy: 1 })
Deno.serve(bitoraDeno(guard, (req) => new Response('hola')))
快递
import { createGuard } from '@bitora/guard'
import { bitoraExpress } from '@bitora/guard/express'
app.set('trust proxy', true)
app.use(bitoraExpress(createGuard({ secret: process.env.BITORA_SECRET, mode: 'enforce' })))
浩诺
import { createGuard } from '@bitora/guard'
import { bitoraHono } from '@bitora/guard/hono'
app.use('*', bitoraHono(createGuard({ secret: process.env.BITORA_SECRET, mode: 'enforce' })))
选项
| 选项 | 默认 | 它会做什么 |
|---|---|---|
secret | 无 | 您网站的 API 密钥 。 没有它什么 发送到比多拉. |
mode | 'observe' | 'observe' 仅报告; 'enforce' 块. |
rateLimit | { max: 300, windowSec: 60 } | 整个网站每个IP的要求. |
routes | [] | 每个IP和路由 : { match, max, windowSec }。用于登录、注册和联系表. |
aiBudget | [] | 需要钱的终点: { match, max, windowSec, key }。 。 。 . key(req) 返回用户 ID; 否则将使用 IP. |
bots | { paths: [], block: true } | 在这些路径上已知的自动化(curl,脚本,无头浏览器). |
skip | 静态文件 | 从未检查过的路径前缀 . |
ipHeader | 无 | 例如,您自己的代理服务器头和访客不能 cf-connecting-ip 云发后. |
trustProxy | 0 | 你有多少代理人坐在应用程序前? 地址是很多地方的地址 x-forwarded-for。 。 。 . |
remote | { enabled: true, mode: 'list', refreshMs: 30000, blocking: false, timeoutMs: 100, cacheMs: 60000 } | 'list' 下载 Bitora 想要被屏蔽的地址 refreshMs 并在记忆中检查它们(每次刷新一次电话,无论来访者多多). 'ip' 询问每个新地址并缓存答案最多 cacheMs。不屏蔽:没有请求等待比多拉. |
report | { enabled: true, flushMs: 20000, maxBatch: 50 } | 分批发送给比多拉. |
大赦国际支出
guard.ai.wrap(client, { user }) 返回您的 OpenAI 或 Anthropic 客户端 create() 以符号计的电话:
const openai = guard.ai.wrap(new OpenAI(), { user: () => session.userId })
await openai.chat.completions.create({ model: 'gpt-4o-mini', messages }) // throws GuardBudgetError (status 429) when a budget is spent
在调用之前,它收取一个估计数(请求字符的四分之一加上它可能要求的产出),然后是估计数
与 usage 回答时的答案。 当执行时,该请求也会要求限制输出(ai.maxOutputTokens) (中文(简体) ).
适用两个预算: ai.perUser (每个用户,或者每个IP,如果你不给)和 ai.global整个场地的上限
保护你的钱不花掉 接通的电话保持了估计值,因为这些电话的使用不为人所知.
过 { ip, ipSource } 将拒绝通知比多拉 柜台在记忆中,每个实例.
报名
guard.signup({ email, ip }) 返回时 { action: 'allow' | 'block', reasons }它检查地址的形状,是否包括:
域名是一个丢弃的域名( 一个内置的短列表, 加上 signup.extraDisposable),以及有多少注册来自相同的
地址(signup.perIp和同一网络,a /24或a /64(signup.perNetwork) (中文(简体) ). 在你的注册处理器中调用它
创建账户.
地址来自哪里
访客的地址从不读取访客可以写出的信头. 它来自: 您在标题 ipHeader在韦尔塞尔, x-vercel-forwarded-for· ; trustProxy 从右侧输入 x-forwarded-for;或者,在节点上,套接字(当套接字是私人地址时,因为您的代理在前面,是最后一个输入) x-forwarded-for) (中文(简体) ). 没有一个地址是未知的:没有任何地址加以限制或报告,事件没有声誉。 每一次活动都说其地址来自何处(ip_source) (中文(简体) ).
柜台在每一个实例的记忆中直播. 在一个没有服务器的平台上,每个实例本身都算数,所以 限制是每个实例的上限,而不是全球性的.
它不能做的是:它不能保护从未到达您的服务器的流量(例如浏览器直接与 中输入.