ji ruo
|
0af7132ab7
|
修复计划暂停来源和时间线刷新
|
2026-10-10 09:16:28 +08:00 |
|
ji ruo
|
87e62fb904
|
支持计划任务批量选择操作
|
2026-10-10 09:01:21 +08:00 |
|
ji ruo
|
96a10878ad
|
重新设计计划任务运行时间线
|
2026-10-10 08:56:13 +08:00 |
|
ji ruo
|
8a1c9debbb
|
让立即运行直接恢复任务
|
2026-10-10 08:50:15 +08:00 |
|
ji ruo
|
8067f3286b
|
修复计划任务立即运行并区分状态颜色
|
2026-10-10 08:45:06 +08:00 |
|
ji ruo
|
a5cfec025f
|
固定显示计划任务时间线入口
|
2026-10-10 08:39:55 +08:00 |
|
ji ruo
|
e52fe67171
|
将计划任务改为字段表格展示
|
2026-10-10 00:47:14 +08:00 |
|
ji ruo
|
aed3062b4d
|
增加计划任务检索分页和时间线
|
2026-10-10 00:38:22 +08:00 |
|
ji ruo
|
30db68a86a
|
优化计划任务列表展示
|
2026-10-10 00:30:51 +08:00 |
|
ji ruo
|
98321d8262
|
支持计划任务日期范围
|
2026-10-09 23:50:05 +08:00 |
|
RuoJi6
|
6a2bba4637
|
Merge pull request #3 from RuoJi6/codex/scheduled-tasks
新增日历式计划任务
|
2026-10-09 23:21:06 +08:00 |
|
ji ruo
|
3254c90a03
|
新增日历式计划任务
|
2026-10-09 23:05:44 +08:00 |
|
ji ruo
|
9adbd54627
|
ci(release): allow replacing release when tag moves
v0.3.16
|
2026-10-09 14:11:39 +08:00 |
|
ji ruo
|
aca6e9a8fb
|
fix(ui): match folder task labels to existing table rows
|
2026-10-09 13:32:57 +08:00 |
|
ji ruo
|
012030b41d
|
fix(ui): align merged folders with existing columns and omit duplicate headers
|
2026-10-09 13:20:53 +08:00 |
|
ji ruo
|
ada09f6c1a
|
fix(ui): restore original controls and limit folders to merged findings
|
2026-10-09 13:12:54 +08:00 |
|
ji ruo
|
d36812b7e1
|
feat(findings): show consolidation across report task and asset views
|
2026-10-09 12:41:46 +08:00 |
|
ji ruo
|
d68594e81a
|
fix(ui): hide completed finding review history from findings page
|
2026-10-09 12:27:54 +08:00 |
|
ji ruo
|
7db7b71cdb
|
fix(findings): protect historical reports and verify consolidation results
|
2026-10-09 12:23:11 +08:00 |
|
ji ruo
|
71ec0f0efd
|
docs(release): prepare v0.3.16
|
2026-10-09 11:14:57 +08:00 |
|
RuoJi6
|
e77c3ae9a3
|
Merge pull request #2 from RuoJi6/codex/finding-folders
feat(findings): 同一漏洞重复上报归并与文件夹展示
|
2026-10-09 11:07:16 +08:00 |
|
RuoJi6
|
444cf6e50a
|
Merge pull request #1 from RuoJi6/codex/http-basic-auth
feat(auth): 添加可选的 HTTP Basic Auth 访问验证
|
2026-10-09 11:05:40 +08:00 |
|
ji ruo
|
24d4726aee
|
feat(findings): consolidate duplicate reports into expandable cases
|
2026-10-09 10:43:53 +08:00 |
|
ji ruo
|
ead58f0983
|
fix(auth): keep config persistence off the request lock
|
2026-10-09 10:39:58 +08:00 |
|
ji ruo
|
04c8c54ff8
|
feat(auth): add optional HTTP Basic Auth access gate
|
2026-10-09 10:12:27 +08:00 |
|
RuoJi6
|
979e1fe4eb
|
Update README.md
|
2026-10-09 09:26:37 +08:00 |
|
RuoJi6
|
735dd61564
|
Update README.md
|
2026-10-09 09:26:23 +08:00 |
|
ji ruo
|
a91b27feb7
|
fix(release): retain upstream version 0.3.15
v0.3.15
|
2026-10-09 09:03:33 +08:00 |
|
ji ruo
|
dfef372b64
|
feat(release): publish standalone repository and Docker Hub images
|
2026-10-09 09:01:25 +08:00 |
|
ji ruo
|
714b00cccb
|
feat(update): publish fork releases for online self-update
|
2026-10-09 08:45:33 +08:00 |
|
Autumn-27
|
e6ec569991
|
docs(changelog): 定版 0.3.15
|
2026-10-07 08:54:02 -04:00 |
|
Autumn
|
fd86f3ceca
|
Merge pull request #197 from Autumn-27/fix/auth-init-fail-open
fix: 密码初始化/登录接口在数据库读失败时 fail closed
|
2026-10-07 19:47:48 +08:00 |
|
Autumn-27
|
a951e4aad3
|
fix bug
|
2026-10-07 07:45:28 -04:00 |
|
Autumn
|
b55ceb1fdd
|
Merge pull request #191 from Autumn-27/feat/judge-token-usage
feat(intercept): 模型兜底审批单独计量 token 并在配置页展示
|
2026-10-04 14:43:54 +08:00 |
|
Autumn-27
|
86729b65dc
|
feat(intercept): 模型兜底审批单独计量 token 并在配置页展示
兜底审批(LLM judge)调模型判定漏网命令,其 token 消耗此前无法单独查看:
usage 行要么无 worker 归属、要么被并进复用的模型 profile 桶里。
- llmrec 增加 WithWorker 上下文覆盖,reviewer 调用前标注 worker='judge',
让审批调用的计量行可被单独筛出(复用现有 llm_usage.worker 列,无需迁移)。
- db.JudgeUsageStats 返回累计总量 + 近 N 天每日序列(WHERE worker='judge')。
- 新增 GET /api/intercept/judge/usage。
- 配置页「模型兜底审批」开关下方新增用量卡片:调用次数 / 输入 / 输出 /
缓存读写 五项统计 + 近 30 天每日消耗迷你柱图 + 刷新。
|
2026-10-04 02:43:26 -04:00 |
|
Autumn
|
d0033724a6
|
Merge pull request #189 from Autumn-27/fix/sse-same-origin
fix(web): SSE 活动流默认同源,修复反代部署连不上 :8787
|
2026-10-03 08:10:35 +08:00 |
|
Autumn-27
|
e2c8a23a98
|
fix(web): SSE 活动流默认同源,修复反代部署连不上 :8787
sseUrl() 原先无条件把 SSE 地址拼成 ${hostname}:8787,连生产静态导出
也带这个硬编码。反代到 443 的部署里页面从 https://域名/ 加载,SSE 却被
指向 https://域名:8787/...,而 8787 未对公网开放,导致活动流连不上、反复重连。
改为用 NODE_ENV 区分:生产默认同源(相对路径),仅 next dev 保留 :8787
以绕过 Next.js /api 反代对 SSE 的缓冲。NEXT_PUBLIC_SSE_BASE 覆盖口保留。
验证:NEXT_EXPORT=1 next build 后生产包里 :8787 分支已被 tree-shake。
补充 README 反向代理部署文档(同源说明 + nginx 关缓冲配置)。
Closes #169
|
2026-10-02 20:10:08 -04:00 |
|
Autumn
|
160fe13c24
|
Merge pull request #187 from Autumn-27/fix/notify-digest-and-mask
fix(notify): 汇总模式退化、目的地留空误判、SMTP 缺 SSRF 守卫
|
2026-10-01 19:00:43 +08:00 |
|
Autumn
|
314105e827
|
Merge pull request #185 from lucksec/feat/vuln-im-push
feat(notify): 漏洞发现支持推送到钉钉/飞书/企业微信等六种渠道
|
2026-10-01 18:58:50 +08:00 |
|
Autumn-27
|
5081d95b99
|
fix(notify): 邮件渠道的 SMTP 拨号补上 SSRF 守卫
emailDial 用的是裸 net.Dialer,没挂 blockInternalDial,Validate 也只检查
host 非空。于是 SMTP 是整套 SSRF 防护里唯一的缺口:host 填 169.254.169.254
或 127.0.0.1 能直接连上,而 smtp.NewClient 握手失败时会把对端返回的那一行
(textproto 的 short response)包进错误、经 last_error 由投递历史接口回显,
正是其它渠道已经关掉的半盲读原语;「连接被拒 vs 超时」的耗时差异还能
用来探测端口。
拨号阶段是最终生效点,也覆盖 DNS 重绑定,与 HTTP 系渠道共用同一道守卫。
RFC1918 私网继续放行,与 isBlockedDialIP 既有的取舍一致——内网自建 SMTP
中继是常见的合法部署。
这个缺口没有测试会发现:本包 TestMain 全局打开了 ARTEX_NOTIFY_ALLOW_LOCAL,
邮件用例无论守卫在不在都会通过。新增的两条用例自己清掉该变量,
并配一条反向用例确认显式放开后本机 SMTP 仍可投递。
|
2026-10-01 06:43:48 -04:00 |
|
Autumn-27
|
77834f0080
|
fix(notify): 目的地字段留空不应被判成地址变更
Telegram 渠道从第二次保存起会永久失败,用户什么都没改也存不上:
新建时 base_url 留空(用官方 API 地址),创建路径直接存下 base_url:""
→ 第一次保存,MergeConfig 把空串当显式清空、delete 掉该键
→ 第二次保存,前端照常提交 base_url:"",而 stored 里已经没这个键
→ sameConfigValue("", nil) 比较 `""` 与 `null`,判为不等 → changed=[base_url]
→ bot_token 是掩码回显值 → 400「目标地址已变更,请同时重新填写凭据字段」
此后每次保存都失败,除非重新粘贴一遍 Bot Token,而错误提示还把人
往「凭据有问题」上引。
根因是 MergeConfig 与 sameConfigValue 对「空」的口径不一致:前者认为
空串就是「该删掉这个键」,后者认为 "" 与键不存在是两个不同的值。
现在比较前先归一化,口径与 MergeConfig 的清空判定对齐。
这是上一轮修「改目标地址、保留凭据」那条凭据外发路径时引入的回归——
守卫本身是对的,只是把一个没变的空字段误判成了地址变更。配套用例覆盖了
两个方向的真实变更(空→自建反代、自建反代→空)仍必须要求重填 Token,
确认防护没有被削弱。
|
2026-10-01 06:43:48 -04:00 |
|
Autumn-27
|
93c1d39df3
|
fix(notify): 汇总模式按消息计量限流,批次大小不再被每轮请求预算掐死
令牌桶的额度此前被同时当成两个量纲用:对实时模式它是「发几条消息」,
对汇总模式却被直接当成「一批装几条漏洞」传给 ClaimDigestBatch。
notifyMaxSendsPerChannelPerTick(=5)是由租约时长倒推的 HTTP 请求预算,
而一个汇总批次只发一次请求,两者本不该相乘。稳态下更糟:rate_per_min=20
的渠道在 3 秒的 tick 里只补得到 1 个令牌,于是每条汇总消息只装 1 个漏洞,
头部还写着「近 30 分钟新增 1 个漏洞」——digest 退化成带汇总文案的实时推送,
MaxDigestBatchSize(500) 永不可达。
改为统一按**消息条数**计量:一批漏洞合成一条消息、消耗一个令牌,
rate_per_min 对 digest 依然生效(每分钟最多这么多条汇总消息),
而批次大小只受 MaxDigestBatchSize 这个内存上界约束。
ClaimDigestBatch 原先的注释声称「单批同时受限流额度与内存上界约束」,
那正是本问题的来源——限流额度本不该充当批次大小。注释一并改掉,
免得下次又按「rate_per_min 对 digest 不生效」把它改回去。
顺带让 takeTokens 接受 want 上限:原先整桶抽空后才由调用方截断,
多出来的令牌(飞书默认 100/min 时是 95 个)直接丢弃,渠道这一轮
没有待发投递时也照扣,注释声称的突发容量在任何情况下都做不到。
决策收在 digestTickPlan 里由新增用例直接钉住。现有端到端用例都手动给
stepDigest 传一个够大的 limit,绕过了 step 里的额度计算,所以覆盖不到这里。
|
2026-10-01 06:43:29 -04:00 |
|
 luckoneandClaude Code
|
f2f35ebc2e
|
fix(notify): 复测判定「已修复」时也要推送状态变更
复测结论为 fixed 时,db/finding_retests.go 里是裸的
`UPDATE findings SET status=...`,绕过了带通知的版本——于是配了
on_status_change 的渠道对这种状态流转完全收不到推送:状态在界面上悄悄变了,
运维要打开平台才知道。
把「更新状态 + 登记状态变更事件」抽成事务级的 SetFindingStatusTx,供
patchFinding 与复测路径共用,两者的语义(状态没变就不登记、快照带全渲染字段)
由同一份代码保证,不会再出现只改一处的情况。
附回归用例:复测判 fixed 后必须有一条 finding_status_changed 事件,
且快照里的 from/to 与渲染字段正确。
Co-Authored-By: Claude Code <[email protected]>
|
2026-09-30 16:25:06 +08:00 |
|
 luckoneandClaude Code
|
ee8929115e
|
refactor(web): 通知页拆分为组件,1095 行降到 605 行
页面此前接近 1100 行,超过 CLAUDE.md 的 800 行硬上限。按项目的 _components/
约定拆开,每个文件都能单独读懂:
_components/channel-fields.ts 渠道字段表与配置值解析(纯数据,无视图)
_components/channel-form.tsx 按字段定义渲染控件的表单
_components/delivery-list.tsx 投递记录表与筛选
_components/stat-tile.tsx 概览统计卡片
page.tsx 只负责编排:加载、表单状态、调接口
拆分是纯模块搬移,未改行为;tsc 与 next build 均通过,/system/notify 仍正常
预渲染。
Co-Authored-By: Claude Code <[email protected]>
|
2026-09-30 16:25:06 +08:00 |
|
 luckoneandClaude Code
|
a049f09b97
|
fix(notify): 安全审计修复 —— 掩码绕过、SSRF、静默丢失等 12 项
对推送功能做了一轮安全审计,每条都先复现、再修、再补回归测试。
以下按严重程度排列。
【严重】掩码可被「改目标地址、保留凭据」绕过
目标地址与凭据是两套独立字段,而配置合并对「未提及的键」保留原值。于是
只改地址、对凭据避而不谈,服务端就会把库里的真凭据发到任意地址。已实测
复现四个渠道:通用 Webhook 的 Authorization 头、Telegram 的 Bot Token
(进请求路径)、邮件的 SMTP 密码(STARTTLS 后交给对端)、钉钉/飞书的加签
密钥(该项不可利用,一并列出仅为说明面)。完全静默、不依赖重定向,所以
上一轮加的「拒绝跨主机跳转」挡不住它。
修法:新增 Channel.DestinationKeys() 声明「消息发往哪」,PrepareConfigUpdate
在地址变化时要求对**每一个**凭据字段显式表态(给新值,或显式留空表示不再
需要)——原样回传掩码值等于「沿用旧凭据」,正是攻击形状,一并拒绝。
刻意不做「自动丢弃凭据」:对可选字段(如 headers)那会变成「鉴权静默没了
但接口返回成功」,比报错更难排查。
【中】markdown 系三渠道对不可信内容零转义
钉钉/企微/飞书此前完全不转义(Telegram 与邮件都做了),而标题与摘要来自
模型输出、资产 url 来自被测目标。一条标题里的 `[文字](http://evil)` 会在
工程师的钉钉里变成可点击外链;`` 会被客户端拉取,
泄露阅读者 IP 并暴露「这条已被看过」。现统一单行化 + markdown 元字符转义。
修的过程中还纠了自己一个设计错误:转义一度被放进 markdownTitle,而它被
markdown 正文、Telegram 的 HTML、飞书卡片、Webhook 的 JSON 与邮件主题四种
语境共用,markdown 的反斜杠泄漏到了 Telegram 消息里(用户会看到 `\(1\)`)。
转义必须发生在各自的渲染出口——测试直接抓到了这个回归。
【中】投递地址的 SSRF 面
此前只校验 scheme 与 host,云元数据 169.254.169.254、环回、内网一律可投递;
而失败响应体前 200 字节会进 last_error 并被投递历史接口回显,构成半盲读
原语。现在在**拨号阶段**设防(最终生效点,同时覆盖 DNS 重绑定与同主机
重定向)。环回/链路本地需显式 ARTEX_NOTIFY_ALLOW_LOCAL=1 才放行(本机
SMTP 中继是合法配置);RFC1918 私网**刻意放行**——内网自建 Mattermost /
SMTP 很常见,做到那个程度会把正常部署一起废掉,这条取舍写进了测试断言。
【中】汇总消息被截断后整批标记已送达 = 静默丢失
渠道都有长度上限(企微 4096 字节最紧)。此前渲染完整篇再截断、然后整批
标记成功:被截掉的那些既不在消息里、也不在失败列表里,投递历史还显示
成功。现改为按**整条**打包,只标记真正装下的,其余回队续发,消息头部
如实写明还有多少条在下一条。被推迟的条目**不消耗重试次数**(领取时乐观
+1 的那次减回去),否则 500 条的积压会在第三段就把尾部判成失败。
【中】其余修复
- min_severity 打错字让过滤器静默失效(未知级别序数为 0 → rank>=0 恒真,
用户以为「仅高危」实际全推)。改为写入时校验取值。
- rate_per_min=0(不限流)不可达:文档/UI/令牌桶都按 0=不限流解释,唯独
写库那层 `if <=0 取默认值` 把它改成 20 或 100。默认值改到接口层填。
- digest 渠道完全绕过令牌桶(allow 被扣却没人用)。
- 失败处置按整批 max(attempts) 判断,让老投递连坐同批的新投递。
- 汇总批次里快照无法解析的投递被静默标记成功。
- url.Parse 失败时错误文本仍带完整地址(上一轮只修了 http.Client.Do 那侧;
且当时的用例全走 scheme 分支、没覆盖解析失败路径,是假保证)。
- 单渠道每轮投递条数可能超出租约时长(多实例下重复发送)。加了防漂移
断言,写的时候立刻发现原取值 6 恰好用满租约、零余量,已改 5。
- Telegram 截断可能切断 HTML 实体,导致整条被拒收。
- 邮件把 SMTP 4xx(灰名单等临时失败)判成永久失败,最该重试的场景从不重试。
- 提交嵌套结构时会把掩码字面量真的写进库,导致鉴权静默失效。
测试:notify 包从 82% 提到 86%,新增 SSRF 守卫、掩码绕过(用攻击形状的
输入而非「防御逻辑的正确输入」)、分段投递收敛、markdown 转义、SMTP 4xx/5xx
分类等回归用例;端到端补了一条「60 条长标题跨多轮发完且不卡死」的收敛测试。
Co-Authored-By: Claude Code <[email protected]>
|
2026-09-30 16:25:06 +08:00 |
|
 luckoneandClaude Code
|
f3fb37f70d
|
feat(notify): 漏洞发现支持推送到钉钉/飞书/企业微信等六种渠道
新增「系统 → 通知推送」页,把扫描发现的漏洞推送到钉钉、飞书、企业微信、
通用 Webhook、Telegram、邮件。同一类型可配任意多个机器人实例,每个实例独立
设启停、限流与过滤规则(最低级别 / 任务资产范围 / 漏洞类型关键词 / 是否接收
处置状态变更)。推送时机分实时与汇总两种模式,「高危实时、其余汇总」建两个
渠道即可表达,策略不写死在代码里。带投递历史与失败重发。
三个关键设计:
1. 写漏洞的事务只做一次盲 INSERT。notification_events 由 RecordFindingTx
在同一事务内写入,保证「漏洞落库」与「推送任务存在」原子一致;这条 INSERT
刻意不读渠道表、不跑用户的过滤规则,否则一条配错的过滤条件就能污染甚至
中止事务,让高危漏洞存不进库。为把失败隔离在这一条语句上(PostgreSQL 中
事务内任一语句报错会让整个事务作废、连 COMMIT 都失败),它被 SAVEPOINT
包住,失败时只记日志。
2. 投递用租约领取而非长事务。FOR UPDATE SKIP LOCKED 领取后把行置为 sending、
把 next_attempt_at 推到未来作为租约,提交后再做网络投递,因此投递期间不
持有数据库锁;崩溃留下的 sending 行会被下一轮重新领取,自愈且不会无限重试。
3. 限流不消耗重试预算。先按渠道令牌桶算出本轮还能发几条,再按这个数量领取;
顺序反过来会让被限流挡下的投递白计一次尝试,三次预算被等待耗光后落入失败。
审计并修复的四个问题:
- 推送凭据经错误信息外泄:http.Client.Do 返回的 *url.Error 会把完整 URL 打进
错误文本,而钉钉 access_token、企业微信 key、飞书 hook id、Telegram
/bot<token>/ 都就在 URL 里。该串流入 last_error(明文落库)、投递历史接口
(绕过渠道掩码)、服务端日志与前端错误提示。现统一脱敏为 scheme://host
+ 底层原因,并拒绝跨主机重定向。
- 最低级别门槛打错字会让过滤器静默失效:未知级别序数为 0,判定退化成
rank >= 0 恒真,用户以为限制「仅高危」实际全推。现于写入路径校验取值。
- 汇总批次无上限:一次领取把全部待发行读进内存、消息被渠道长度上限截掉大半
且静默丢失。现单批上限 500 条,超出部分留库成为下一批。
- 邮件渠道把 SMTP 4xx(灰名单等临时失败)判成永久失败,导致这类最该重试的
场景从不重试。现按应答码首位区分 4xx/5xx。
测试:notify 包 82% 覆盖(含用自建最小 SMTP 服务器驱动的完整邮件协议会话、
两种签名算法对 OpenSSL 基准值的比对、UTF-8 边界截断、以及模板能力边界的
反射断言);db 与 server 侧共 29 个用例跑真实 PostgreSQL,其中端到端用例
真发 HTTP 到假接收端并断言消息内容与投递状态。
Co-Authored-By: Claude Code <[email protected]>
|
2026-09-30 16:25:06 +08:00 |
|
 luckoneandClaude Code
|
18589b270e
|
fix(db): 修正公司 ICP 归属用例连接关闭顺序导致的资产残留
清理注册在 t.Cleanup 里,而连接关闭用的是 defer d.Close()。defer 在函数
返回时先执行、t.Cleanup 在其后,于是两条 DELETE 全部落在**已关闭的连接**
上,错误又被 `_, _ =` 丢弃,测试资产与公司因此永久残留在库里。
该用例用 MAX(companies.id)+1 当假 TaskID 给资产打标。残留本身不会立刻报错,
但一旦这个数字与其他用例的任务 id 相撞,那个按「恰好 N 个资产」断言的用例
就会失败,而现象(某个无关测试的资产计数不对)与原因(另一个测试的连接
关闭顺序)相距极远,排查成本很高。
改为关闭连接也走 t.Cleanup 并注册在清理之前(t.Cleanup 后进先出,保证清理
先跑),同时让清理失败显式报错而不是被吞掉。
注:同一模式(defer d.Close() + 在 t.Cleanup 里写库)在 db 包另有十余处,
本次只修被实证触发的那一处。
Co-Authored-By: Claude Code <[email protected]>
|
2026-09-30 16:24:47 +08:00 |
|
Autumn
|
50b7b2a05b
|
Merge pull request #184 from Autumn-27/fix/finding-asset-tree-scroll
fix(findings): 「按资产」视图资产列表溢出时补回滚动条
|
2026-09-30 16:17:08 +08:00 |
|
Autumn-27
|
4b1eda8e92
|
fix(findings): 「按资产」视图资产列表溢出时补回滚动条
左侧资产树的滚动容器靠 height:100% 取高,但外层卡片只给了 max-height
没有确定高度,百分比高度解析失败,资产多时列表撑破卡片或被截断且无法
滚动。改为把高度上限直接放到资产树的原生滚动容器上(overflow-y-auto +
max-h,随窗口自适应),资产少随内容收缩、资产多封顶并出滚动条。
Closes #170
|
2026-09-30 04:16:43 -04:00 |
|
Autumn
|
b590898d1f
|
Merge pull request #183 from Autumn-27/feat/intercept-delete-path
feat(intercept): 新增内置拦截规则「删除类接口路径」
|
2026-09-30 16:15:15 +08:00 |
|