昨天申请并通过了OpenAI的个人认证,加上前段时间以开源贡献者的身份申请了OpenAI和Anthropic的赞助,分别获取了两家的max会员半年,我会写几篇文章,分别分享一下Codex Security的使用体验、Codex和Claude Code的对比、GPT Cyber模型的使用体验等。
这篇文章先介绍一下Codex Security的使用体验,因为扫描额度有限,我只测试了几个仓库,并以我自己开发的CoNote2进行介绍。事先说明,这篇文章的评测纯属主观,没有严谨的对比测试,仅供参考。
AI Agent对安全领域的颠覆性改变
在写测试之前,我先说下这半年AI Agent对安全领域的颠覆性改变。去年年中到下半年,当时Opus的版本还是4,Opus 4.5还没有发布,Cursor正是如日中天的时间,但经过测试我发现,如果用此时的大模型来挖洞,挖出来大量是误报的问题,特别是对于大型项目分析起来还是很吃力。
此时我们团队内部其实在做的完全是另一件事——使用AI来处理和运营传统SAST工具生产的漏洞。我对自己的这个决定也沾沾自喜,我的想法是,如果让大模型自己去挖洞,我不太看好;但如果只是让大模型审核我们传统SAST产出的漏洞报告,那就完全不一样了,大模型只需要跟着漏洞报告中说到的数据流进行分析就可以了。
为此,我还专门做了一份PPT给老板们介绍,我想我们做的这个东西即发展了大模型的特长,又规避了大模型的问题(这里的问题主要指的是在长上下文时的注意力问题),结合传统SAST效果确实不错。
不过在介绍这个方案的同时,我还是提出了这个方案的缺点,就是传统SAST无法检测的漏洞,现在的方案仍然无法检测。这个方案解决的是准确率的问题,但无法提升召回率,更好的方案还是让AI Agent完全接管代码审计这个任务,不过我当时对第二个方案十分缺乏信心。
但老板还是鼓励我们去做了,也提供了很多资源,于是去年10月份我们开始做自己的白盒Agent。半年以后再往前看,当时我的想法实在太过保守,Agent的工作模式对于传统SAST是颠覆式的改变——我们在只使用Gemini Flash模型的情况下,Agent就已经可以挖掘出大量高危及严重的漏洞了,现在已经逐渐成为漏洞贡献的主力军。
言归正传,Codex Security也是GPT推出的一个云端代码审计Agent,我们今天就来体验一下。
用户界面
Codex Security是一个完全云端的Agent,在本地的Codex App里没有这个功能,必须登录网页端才能看到。
界面设计的挺难用的,我一度找不到怎么创建任务,因为他的主页显示的是漏洞列表……但是我还没有创建任务,怎么会有漏洞呢,所以登录进去看到的将是一篇空白。
点击左侧侧边栏第二个Scans标签页以后,才能看到任务列表和创建任务的按钮。创建任务只能选择自己的Github中的代码仓库,所以如果想扫描私有项目,需要先将代码上传到自己的仓库里才能开启扫描。
我选择扫描了一下我以前写的CoNote2:
创建任务的时候可以填写我比较关注的漏洞类型、AI应该着重分析哪些部分以及这个项目的一些介绍。我全部留空了,因为我这个项目是一个完整的包含了前后端的项目,理论上我不应该人工定义任何内容。在实际企业场景下,安全工程师创建任务的时候也不会给每个仓库都设置一个自定义的prompt,所以就按照默认的来就可以了。
创建任务以后,需要排队等待,等了差不多一个中午,下午回来的时候就发现漏洞已经扫描结束。
扫描报告
和我们的Agent有一点不同,Codex Security会分析仓库的历史commit,但我不太清楚它具体会分析什么。
漏洞报告首先会给出这个仓库的介绍,以及资产、安全边界、各个模块作用等信息,最后还会给出它对于严重、高中低危漏洞的定义是什么:
这份报告找到了1个严重漏洞,7个高危漏洞,我们就来分别看看这些漏洞是否是准确的。
严重漏洞
这份报告里唯一一份严重漏洞是“QueryChain drops WHERE clauses, enabling auth bypass and IDOR”:
大概意思就是,我使用gorm的时候,在Where函数返回后没有把返回值保存下来,会导致这个Where条件没有应用到SQL语句里,最后导致权限校验被绕过。
我的原始代码是:
func (c *QueryChain[T]) handleWhere(tx *gorm.DB) {
for _, w := range c.wheres {
tx.Where(w.where, w.args...)
}
}
它给出了Patch,代码是:
func (c *QueryChain[T]) handleWhere(tx *gorm.DB) *gorm.DB {
for _, w := range c.wheres {
tx = tx.Where(w.where, w.args...)
}
return tx
}
这很明显是一个误报,codex对于gorm的底层代码可能并没有严格去分析。gorm在调用Where函数后,并不需要将其返回的tx保存下来,而是会直接修改用户传入的tx变量。
在漏洞界面上,我们可以点击“Chat”并填写一些问题,并让GPT二次分析这个报告。于是我提出了个问题“你确定gorm会返回一个新的DB对象,但不会修改原始的DB对象吗?请你进入gorm的源码看一下”:
结果codex在云端agent里重新验证了一遍,甚至下载了gorm的源码进行审计,然后再次给出了肯定的答案——这就是一个漏洞:
我在claude code里让opus 4.6模型阅读了一下这个报告(使用chrome-devtools这个mcp),它除了检查代码以外,还实际编写临时代码进行了测试,最终给出了误报的结论:
我给的提示词非常简单:“帮我使用浏览器看下这个链接里讲的漏洞,是否是真实漏洞:漏洞URL链接”。
很明显,要不然就是codex这个agent本身做的不太行,要不然就是云端agent缺失一些功能。
高危漏洞
既然严重漏洞是误报,我们看看那7个高危漏洞都是什么。
由于数量太多,我先把这7个漏洞标题列在这里:
- SMTP log keyword filter may bypass user scoping
- XSS report keyword filter can bypass user scoping
- File list filter bypasses user scoping via OR clause
- Web log search filter bypasses user scoping with OR clause
- FTP downloads allow unauthenticated access to user files
- Unauthenticated /api/initialize enables remote admin takeover
- Default config ships fixed JWT secret enabling token forgery
SMTP log keyword filter may bypass user scoping
这个漏洞,Codex Security给出的问题如下:
Introduced cross-tenant information disclosure risk in SMTP log filtering due to an OR condition not being parenthesized with the user_id constraint.
The updated SMTP log filter first applies
user_id = ?and then appends a keyword condition containingusername LIKE ? OR emails::text LIKE ?. In GORM, successiveWherecalls are concatenated withANDand do not automatically wrap raw SQL containingOR. This can yield SQL likeuser_id = ? AND username LIKE ? OR emails::text LIKE ?, which is equivalent to(user_id = ? AND username LIKE ?) OR emails::text LIKE ?. A user can supply a keyword that matches another user's email, resulting in cross-tenant disclosure of SMTP logs, which may contain sensitive content and credentials.
这很明显又是一个对于gorm不熟悉导致的问题。Claude Code给出了很明确的解释:
gorm会自动给每个Where函数中生成的SQL语句增加括号,而不是像Codex Security说的那样“In GORM, successive Where calls are concatenated with AND and do not automatically wrap raw SQL containing OR”。
这也是一个误报。
XSS report keyword filter can bypass user scoping
这个漏洞和上一个漏洞完全一样,只是出现在不同模块,属于误报。
File list filter bypasses user scoping via OR clause
这个漏洞和上一个漏洞完全一样,只是出现在不同模块,属于误报。
Web log search filter bypasses user scoping with OR clause
这个漏洞和上一个漏洞完全一样,只是出现在不同模块,属于误报。
FTP downloads allow unauthenticated access to user files
这个漏洞,Codex Security的描述是:
This commit introduces unauthenticated FTP file access by wiring the FTP server into the main app and implementing RETR/SIZE to return database-backed file contents without any password validation or authorization checks.
The new FTP handler maps the USER argument to a domain and sets userID without verifying credentials, while PASS always succeeds. Subsequent SIZE/RETR commands read file content from the database based solely on that userID and stream it over the data connection. Since the FTP service is now wired into the main application lifecycle, any network client can choose a victim domain in USER and download files without authentication or user-enabled checks, resulting in cross-tenant data exposure.
其实Codex Security的分析也不算错,CoNote2中我确实设计了一个匿名FTP服务器。但这个FTP和HTTP服务器一样,用于储存并运行用户上传的文件,本来就是公开的文件,所以也谈不上未授权访问了。
这个漏洞不能算误报,但算作是AI没有完全理解我的仓库功能导致的误判。Claude Code也分析的比较透彻:
Unauthenticated /api/initialize enables remote admin takeover
这个漏洞,Codex Security的描述是:
Introduced a public, unauthenticated initialization endpoint that grants superuser privileges when no users exist, enabling remote admin takeover on fresh deployments.
The new /api/initialize route is registered before UserCheckerMiddleware, so it is reachable without authentication. InitializeAdministrator only checks that no users exist and then calls UserCreate with admin=true, which sets IsSuperuser on the new account. On an uninitialized instance (or during setup), any remote attacker can hit this endpoint and become the first admin, gaining full control of logs, domains, and configuration. This is a privilege escalation/authentication flaw introduced by adding a publicly reachable bootstrap endpoint without a one-time token, local-only restriction, or other guard.
这个漏洞Codex Security分析的没问题,我在设计CoNote2的时候,为了让用户初始化更加方便,所以设计了一个类似于其他CMS的“安装”功能:当发现数据库里没有管理员用户时,第一个访问CoNote的人可以创建一个管理员用户。
正如Claude Code的分析那样,这是行业里很常见的 "first-user-becomes-admin" 引导模式(GitLab、Gitea、Grafana 早期版本、Jenkins默认配置都用过类似套路),这个设计是我预定义的,并不是代码的Bug:
不过,如果可以对初始化的流程增加一个IP限制或token限制的话将会更好。
Default config ships fixed JWT secret enabling token forgery
这个漏洞,Codex Security的描述是:
Introduced insecure default configuration: a hardcoded JWT secret in default-config.yaml combined with entrypoint logic that skips rotation when an existing config is supplied.
The commit introduces default-config.yaml with a static secret_key value and an entrypoint that only replaces this placeholder when config.yaml does not already exist. In common Docker deployments (e.g., bind-mounting a config copied from the default template), the placeholder remains unchanged. Because JWTs are signed with config.SecretKey and config validation only checks for non-empty values, an attacker who knows the default secret can forge tokens and authenticate as any user or admin, leading to full account takeover.
我在编写CoNote2代码的时候,为了方便后续部署时进行初始化,所以放了一个默认的配置文件default-config.yaml在根目录下,这个配置文件里有一个默认的secret key,值是“DEFAULT_SECRET_KEY”。
其实我在编写Dockerfile时,在ENTRYPOINT脚本里对这个默认值进行了替换:
...
generate_urandom() {
local length=$1
tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c "$length"
echo
}
if [ ! -f "config.yaml" ]; then
cp default-config.yaml config.yaml
sed -i "s/DEFAULT_SECRET_KEY/$(generate_urandom 32)/g" config.yaml
sed -i "s/DEFAULT_SHORTENER_SALT/$(generate_urandom 12)/g" config.yaml
fi
如果用户不是使用docker部署,然后自己也没有修改默认的secret key,确实会存在问题。Claude Code也给出了建议:
总结
其他的中低危漏洞就不看了,总结一下这几个漏洞:
- GORM 语义类(QueryChain / SMTP / WebLog / XSS / File 的 OR bypass):全是 AI 不懂 GORM v2 的 getInstance() + 单 Where 自动加括号语义导致的误报 —— 5条可以全部 dismiss
- 产品模型类(FTP 无密码下载):AI 不懂 conote2 是公开 payload 投递平台,domain_key 是路由不是认证 —— 误报可 dismiss
- 引导期/默认值类(/api/initialize + DEFAULT_SECRET_KEY):真问题,都是和"首次部署"相关的加固项,建议联合修掉(env-based config + strong validation + 首启动 token)
前面5个全部是明确的代码上的误报,第6个是不理解产品导致的误报,第7、8个是“最佳实践”类型的问题,我也可以认为是无法利用的问题。
由此可以看来,至少Codex Security在审计代码这一方面还有待提升。











