【免费下载链接】freehire
freehire — the open-source search engine for job seekers
freehire 是一套服务端应用,由公开 HTTP API、独立的抓取/富化 worker、PostgreSQL 数据库与 Meilisearch 索引共同组成。本篇文章以仓库根目录的 SECURITY.md 为骨架,逐条解读其信任边界、漏洞报告流程与 In/Out of Scope 划分,并结合仓库内真实源码(JWT 签发、账户链接、moderator 门禁、linksource 解析、LLM 富化清洗等)剖析每一条安全承诺背后的实现依据。读完你将清楚:什么算 freehire 的安全漏洞、应当如何私密上报、哪些情况(如自托管配置不当)不在项目责任范围内,以及"LLM 富化输出被污染"为何被设计为可过滤而非漏洞。
freehire 的系统构成与信任边界
按 SECURITY.md 的界定,freehire 由四类组件构成:
- 公开 HTTP API:面向浏览器与扩展提供职位检索、账户、订阅等接口;
- 独立的抓取/富化 worker:从各类招聘源抓取职位、解析正文并做 LLM 富化,典型入口见
cmd/下的 ingest、tg-ingest 等命令; - PostgreSQL 数据库:存储用户、职位、订阅等全部业务数据(migrations 目录展示了完整的库表演进);
- Meilisearch 索引:承担职位全文/语义检索。
信任边界的含义是:worker 抓取的网页正文、用户提交的表单内容、LLM 的富化输出,都属于"不可信输入",只有经过认证、授权、校验和清洗的数据才能进入数据库与索引。整份安全策略其实就是围绕"如何守住这条边界"展开的。
漏洞报告流程:必须私密上报
安全策略明确要求:疑似漏洞必须私密报告,而不是开公开 issue。两条上报通道:
- 通过本仓库的GitHub Security Advisories(Security → Report a vulnerability)提交私有报告;
- 邮件联系strelov1@gmail.com。
报告需要包含四类信息:
- 问题描述及其影响(impact);
- 复现步骤、概念证明(proof of concept)或相关日志;
- 受影响的 endpoint、worker、依赖包、commit SHA或配置;
- 已知的缓解措施(如果有)。
这条要求对应仓库内"修复闭环"的工程实践:报告中给出 commit SHA 与精确 endpoint,能帮助维护者快速定位到internal/api/handler/下具体 handler、cmd/下具体 worker 或某份配置,缩短从确认到修复的链路。
In Scope:项目明确负责的攻击面
认证与会话:JWT cookie、API key 与 OAuth 授权码流
策略点名的三类认证机制——JWT 会话 cookie、API-key 认证、授权码 OAuth 流(Google / GitHub / LinkedIn)——在源码中均有对应实现:
- JWT 会话由 internal/identity/auth/token.go 的
Issuer负责。它是无状态 HS256 签名,subject承载用户 id,并带一个私有声明tv(TokenVersion):版本号让无状态 token 仍然"可撤销"——只要更新账号的版本列,旧版本签发的一切 token 全部失效。同时每个 token 携带随机 16 字节的jti(session id)。 - 会话 cookie 的
Domain属性由 internal/identity/auth/domain.go 的CookieDomainForHost决定:按请求 host 归属于配置的 registrable 域名列表(如freehire.me),cookieDomains为空列表时仅限当前 host(开发环境)。 - OAuth 侧,internal/api/handler/auth.go 中的
oauthRegistry按请求 origin 构建 provider 回调,使登录流能在任意被服务的域名上完成;servedHosts限定可被当作 OAuth redirect origin 的精确 hostname——策略注释明确指出:后缀匹配适合 cookie 作用域,却错误地用于 redirect 目标,因此两者刻意分离。浏览器扩展连接流还被extensionRedirectAllowlist收紧到 chromiumapp.org / extensions.allizom.org。 - API key 只保存哈希,明文 token 仅在创建响应中出现一次,internal/api/handler/api_keys.go 的
apiKeyResponse只回传显示前缀(token_prefix)与 scope,永不回传明文。
账户接管:未验证邮箱是硬性禁区
策略把"用未验证的外部邮箱链接/创建账户,或将已有账户 re-key 到另一个身份"列为账户接管向量。源码将这条规则固化为硬约束:internal/identity/accounts/accounts.go 中,ErrNoVerifiedEmail被注释为"链接或创建账户前的硬性要求(unverified email is an account-takeover vector)"。对应的LinkOrCreateByEmail(accounts.go)只在外部身份携带provider 已验证的邮箱时才允许创建无密码账户;密码注册的CreateUser则显式传入emailVerified=false(accounts.go),后续必须通过MarkEmailVerified证明控制权。
授权:moderator 角色门禁
职位创建/编辑是 moderator 专属能力,任何绕过的尝试都属漏洞。internal/api/handler/jobs_moderation.go 的CreateJob/UpdateJob入口通过requireUserID取得当前用户并交给h.moderation服务,而路由注册处(见 auth.go 注释)明确指出RequireRole是真正的权限执行点,"moderator-only UI" 只是表面层。同样,社区帖子关闭等操作也显式挂载mw.moderator中间件(community.go)。策略同时强调"任何跨用户的数据泄漏"都在范围内——即水平越权同样属于漏洞。
SSRF:worker 抓取链路的红线
抓取/link-following worker(cmd/ingest、cmd/tg-ingest、internal/ingest/linksource)会跟随用户可控或第三方可控的 URL,因此是 SSRF 的高发点——被诱导请求内部地址或用于外传元数据。仓库用测试固化了这一防线:
- internal/ingest/linksource/habrcareer_test.go 构造了一个恶意场景:短链
u.habr.com/EVIL重定向到https://169.254.169.254/vacancies/123(云厂商元数据服务地址)。测试断言:重定向到 career.habr.com 之外的主机必须报错而不是解析出职位,防止元数据被外传或内部地址被探测; - internal/ingest/linksource/remoteyeah_test.go 同样以
http://169.254.169.254/latest/meta-data/作为恶意重定向目标验证拒绝行为; - 各来源适配器(如 ashby.go)通过
Match(u *url.URL)严格限定 host 与路径形状,非匹配 URL 直接跳过,重定向跨 host 即拒绝。
注入:API 与 ingest 管线可达的注入点
策略要求"通过 API 或 ingest 管线可达的注入"必须上报。仓库在存储侧做了纵深防御:internal/ingest/moderation/moderation.go 在写入前调用htmltext.Sanitize对描述做允许列表清洗(存储型 XSS 防护),创建/编辑共享同一条Validate契约(moderation.go)——URL 必须是绝对 http(s) 链接,缺失必需字段直接 400,任何脏数据在落库前被拦截。
密钥暴露
属于项目的、可访问freehire.me基础设施的凭据泄露(如数据库口令、Meilisearch master key、第三方服务 token)属于最高优先级漏洞,应直接私密上报。
Out of Scope:哪些情况不算 freehire 的漏洞
自托管部署的配置不当
策略明确把以下"自托管配置不当"划出范围,并指出安全运行自己的实例是使用者责任:
- 弱
JWT_SECRET; - 公网 HTTP 下
COOKIE_SECURE=false(会话 cookie 不以 Secure 标记发送); - 数据库或 Meilisearch master key 暴露。
这正好呼应源码中的设计:cookieSecure由部署配置注入(auth.go),JWT 的强度完全取决于NewIssuer(secret, ttl)传入的 secret(token.go)。也就是说,代码层防线健全,但运行层安全由部署者负责。
无放大/不对称的纯流量淹没(DoS)
简单的流量灌水若没有放大或不对称效应,不属于漏洞;但利用 freehire 自身不对称性的 DoS(例如让 worker 持续抓取攻击者指定的昂贵目标)理论上仍在考察范围内。
LLM 富化的 Prompt 注入:被设计为"可过滤"而非漏洞
这是本策略最有技术特色的一条。worker 的输入是不可信的职位正文,LLM 只负责分类,其输出在存储前经过固定受控词表的清洗:
- internal/ai/enrich/enrichment.go 的
servedScalarEnums定义了对外提供的枚举字段集合(relocation、salary_period、company_type、company_size),这是"服务端可标量集合"的唯一定义处; Validate(enrichment.go)逐一检查这些字段是否落在受控词表中,越界即报错;Sanitize(enrichment.go)则把越界标量直接置空、多值字段只保留已知成员,并对Summary、TimezoneNote、SalaryCurrency等自由文本做长度裁剪(TrimTruncateRunes),同时过滤掉非正数等不合理的薪资值。
因此"让模型吐出垃圾"是预期行为,会被过滤,不构成漏洞——除非能证明它穿越了真实边界(执行代码、泄露密钥、持久化一个越界词表的值)。这一设计把"模型不可靠"转化为"输出不可信但无害",边界非常清晰。
不可达的第三方依赖漏洞
第三方依赖中无法通过 freehire 触达的漏洞不在范围内;提交依赖漏洞报告时必须附上"该问题确实可达"的证据。
聚合职位内容本身
职位数据来自第三方、非本项目创作,因此关于聚合职位内容(如某条职位描述不准确)的报告不属于安全范畴。
Notes for Reporters:最有价值的报告长什么样
策略对报告质量给出明确指引:最有用的报告应针对最新 release 或最新main,演示当前可复现的边界突破并具有真实影响,同时给出:
- 精确受影响的 endpoint 或 worker;
- commit SHA;
- 相关配置;
- 概念证明(PoC)。
从仓库结构看,一条高质量的 SSRF 报告可以精确到internal/ingest/linksource/下某个 source 的 host 校验缺陷,一条认证缺陷可以精确到internal/identity/auth/token.go的 claim 校验逻辑——这正是 SECURITY.md 要求提供 commit SHA 与 endpoint 的用意:让维护者能以最小代价定位并修复。
小结:freehire 的安全姿态
透过 SECURITY.md 与源码的对照,可以总结出 freehire 的三层安全姿态:
- 输入永远不可信:职位正文、用户提交、LLM 输出一律经过校验与清洗后才入库(moderation 的
Validate/Sanitize、enrich 的受控词表); - 边界由代码固话:JWT 版本化撤销、未验证邮箱禁止建户、moderator 门禁、linksource 跨 host 拒绝、API key 只存哈希,全部落在 internal/api 与 internal/identity、internal/ingest 的实现里,并有对应测试(如 habrcareer_test.go)守护;
- 运行责任交给部署者:自托管方必须自行保证
JWT_SECRET强度、COOKIE_SECURE与数据库/索引凭据安全。
对安全研究者和自托管用户而言,这份策略既是漏洞报告的入口,也是一份浓缩的"攻击面地图"——沿着 In Scope 列出的六类问题(认证、账户接管、授权、SSRF、注入、密钥),再对照文中给出的源码路径逐条核实,即可快速形成对 freehire 安全模型的完整判断。
【免费下载链接】freehire
freehire — the open-source search engine for job seekers
相关推荐
Vale 安全策略全解析:漏洞报告、支持范围与信任边界
Vale 安全策略全解析:漏洞报告、支持范围与信任边界 本文基于开源仓库 SECURITY.md https://link.gitcode.com/i/2a7c
开发工具代码质量CLIArkime 安全策略与漏洞报告指南:范围界定、已知限制与防护基线
Arkime 安全策略与漏洞报告指南:范围界定、已知限制与防护基线 Arkime(原名 Moloch)作为开源的大规模全包捕获(full packet capt
网络安全网络后端数据分析AstronRPA 安全策略全解读:漏洞报告流程、重点防护范围与自托管安全基线
AstronRPA 安全策略全解读:漏洞报告流程、重点防护范围与自托管安全基线 导读 本文以仓库根目录 SECURITY.md https://link.git
工作流自动化低代码桌面应用AI Agent后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考