theHarvester 新增发现模块Discovery Module完整接入指南从 Provider 契约确认到源码注册与测试【免费下载链接】theHarvesterE-mails, subdomains and names Harvester - OSINT项目地址: https://gitcode.com/GitHub_Trending/th/theHarvester本指南以 theHarvester 官方文档 How to add a new module 为核心骨架系统讲解如何为这个开源 OSINT 工具新增一个发现源Provider。你将掌握一套可复制的六步流程确认 Provider 契约、实现异步适配器、注册 Source 目录与工厂、配置 API Key、编写离线测试、更新操作员文档并深入理解SourceExecutionReport状态机、AsyncFetcher会话生命周期等底层机制最终让新模块能够被theHarvester -s source正常调用并产出规范化证据。0. 前置准备分支、环境与贡献规范开始实现前请先阅读仓库根目录的 CONTRIBUTING.md其中包含分支策略、开发环境搭建、测试运行方式与 Pull Request 要求。在动手写 Provider 之前还应在项目的 Issues 与已有 Pull Request 中搜索确认是否已经有人实现过同一 Provider或仓库是否已将其标记为失效源例如 tests/lib/test_source_catalog.py 中就有test_dead_threatcrowd_source_is_not_selectable、test_invalid_bitbucket_domain_source_is_not_selectable、test_removed_inert_sources_are_not_supported等契约测试专门防止失效源重新进入可选列表。1. 第一步确认 Provider 契约Provider Contract在写任何代码之前先阅读 Provider 官方的 API 文档和条款明确以下四个关键问题认证字段需要什么凭据是 API Key、Token、账号密码组合还是完全匿名认证方式直接决定适配器如何构造请求头以及是否需要接入第 4 步的凭据体系。请求约束请求速率限制rate limit、分页方式offset / cursor / page number、重试策略、终止行为分别是什么例如百度搜索以pn参数按 10 条一页翻页见 theHarvester/discovery/baidusearch.py 的 URL 生成逻辑。稳定响应字段哪些字段可以稳定地映射为 hosts主机名、emails邮箱、IPs、ASNs、URLs 或 people这是决定ResultRoute声明与 getter 实现的依据。最小请求序列查询单个域名最少需要哪几步请求首页请求、分页、可能的轮询同时注意一个规范红线不要把 Provider 的价格或配额写入仓库文档应当链接到 Provider 自有文档。仓库文档只描述技术集成方式。2. 第二步实现适配器Adapter适配器统一放在theHarvester/discovery/目录下目录下已存在 baidusearch.py、crtsh.py、virustotal.py 等 50 余个现成实现。应当复用项目共享的 fetcher、配置、parser 与结果归一化能力而不是另起炉灶。2.1 适配器的标准形态一个标准的适配器通常提供三部分初始化器initializer接收目标word/domain与结果上限 limit初始化本地结果集合异步process()方法返回SourceExecutionReport | None是整个 Provider 会话的入口只实现实际支持的 getter如get_hostnames()、get_emails()、get_ips()、get_asns()、get_urls()、get_results()等。以最简单的 theHarvester/discovery/subdomaincenter.py 为例它展示了最精简的形态__init__中保存self.word、初始化self.results set()do_search()用AsyncFetcher.fetch_all()发起单次 GET 请求get_hostnames()直接返回结果集合process(proxyFalse)记录代理标志后调用do_search()。稍微复杂的 theHarvester/discovery/baidusearch.py 则展示了更完整的形态process()内部先尝试 Playwright 无头浏览器模拟真实浏览器、携带Core.get_browser_user_agent()失败或缺少playwright库时回退到纯 HTTP 请求get_emails()与get_hostnames()通过共享 parser theHarvester/parsers/myparser.py 从累积的 HTML 中解析邮箱与子域名。需要强调的纪律Provider 没有提供的字段绝不能伪造返回所有结果在返回前必须做归一化normalize与去重deduplicate。归一化工作最终由 runner 侧统一执行见下文第 2.2 节。2.2process()返回值契约理解SourceExecutionReport状态机process()只能返回以下五种结果之一这是适配器与运行器之间的核心契约返回值含义NoneProvider 会话正常完成包括合法返回零结果的情况。SourceExecutionReport(completed, reason)源在自然结束前成功停止例如已达请求的结果上限。SourceExecutionReport(failed, reason)Provider 或传输层故障导致源结束。SourceExecutionReport(rate-limited, reason)终端级限流导致源结束。SourceExecutionReport(partial, reason)Provider 明确确认覆盖不完整。在源码层面theHarvester/lib/source_execution.py 定义了SourceReportStatus Literal[completed, partial, failed, rate-limited]和冻结数据类SourceExecutionReportstatus必须是上述四种之一stop_reason必须是非空字符串否则在__post_init__中直接抛出ValueError。注意适配器上不得定义可变的execution_status或stop_reason字段。运行器在构造适配器后立即通过_reject_removed_execution_fields()检查这两个字段是否存在一旦发现直接抛ValueError见 theHarvester/lib/source_runner.py。运行器run_source()theHarvester/lib/source_runner.py对返回值的最终化逻辑如下process()返回None→ 状态为completed若结果数为 0 且无 stop_reason则自动记录为completedno-results返回SourceExecutionReport→ 采用其状态与 stop_reason若结果数 0 但状态不是completed则自动提升promote为partial保留已归一化的证据传输层代理失败AsyncFetcher.proxy_transport_failed()为真→ 强制failedtransport-errorMissingKeyError→skippedmissing-credentialsasyncio.CancelledError→ 保留已收集结果有结果记partial否则记failed并通过commit_cancelled回调提交后重新抛出其他异常 → 有结果记partial否则记failed异常类型写入 stop_reason。因此适配器作者只需对 Provider 会话本身负责最终状态由运行器统一裁决这正是不要定义可变执行字段的根本原因。2.3 拥有 Provider 会话Own the Provider ConversationProvider 会话指一次源执行相关的完整请求序列首次请求、分页、重试或轮询、最终响应处理。规范要求这个序列有唯一明确的属主并遵守以下纪律复用同一个AsyncFetcher.open_session()连接池、请求头、cookie jar 与选定的代理身份在会话期间保持稳定。运行器会为一次执行固定一个被选中的代理通过AsyncFetcher.proxy_scope()上下文管理器传入适配器要用传入的 proxy 标志打开会话并把借来的 session 以session参数传给共享 fetch 方法只允许最外层属主关闭它。cookie jar 策略当后续请求可能依赖前面响应建立的 cookie 时典型如搜索引擎的会话 cookie保留默认 cookie jar而像接管检测takeover这类刻意相互独立的探测则使用aiohttp.DummyCookieJar()避免一个目标影响另一个目标。作用域隔离一个 session 只服务于一个 Provider 和一个已授权目标绝不在不同源执行或无关目标之间共享 cookie、认证状态或代理身份。取消安全关闭每个属主 session、response、task、connector 时必须保留取消语义——既要覆盖成功完成路径也要覆盖中断路径且都要有对应测试。open_session在core.py中本身就通过drain_tasks_after_cancellation处理了关闭 session 期间再被取消的竞态。生命周期即生命周期阶段把 session 的构建与销毁视为适配器生命周期阶段。除非 Provider 契约明确改变否则保留现有 TLS 与超时策略普通生命周期失败返回SourceExecutionReport而真正的取消CancelledError要让它原样传播。扩展共享 fetcher 接口需谨慎在扩展AsyncFetcher这类共享接口前审计所有位置参数调用点以及每个属主 vs 借用分支新加的可选参数绝不能改变既有调用的语义。open_session的完整签名见 theHarvester/lib/core.py为headers、proxy、request_timeout、cookie_jar、verify新增适配器应尽量只通过这些既有参数表达需求。完成检查completion check文档要求一个特定的离线测试——后一页的结果依赖于前一页建立的状态证明浏览器/会话状态在翻页间被保留外加一条清理断言证明 Provider 会话最终被关闭。这个测试模式在 tests/discovery/test_baidusearch.py 中体现得淋漓尽致PageResponse.requires_prior_navigation标志会让 FakePage 在后页丢失浏览器状态时直接断言失败见 tests/discovery/test_baidusearch.py而test_cancellation_survives_cleanup_failures则验证即使 page/context/browser/manager 四层 close 全部抛异常CancelledError依然原样传播且四个资源都被关闭tests/discovery/test_baidusearch.py。3. 第三步注册 Source目录 工厂双登记适配器写完后需要注册才能被 CLI 识别注册涉及两个文件、两处条目theHarvester/lib/source_catalog.py新增一条_spec(...)目录条目。目录catalog负责提供 CLI 帮助文本、源选择与活动分类activity classification。SourceSpec冻结数据类包含name、routes、activity、retains_unresolved_hostnames四个字段ResultRoute枚举定义了SUBDOMAINS / EMAILS / IPS / ASNS / PEOPLE / URLS / BREACHES七种结果路由theHarvester/lib/source_catalog.pyActivityClass枚举定义了PASSIVE(P0) / DNS(P1) / DIRECT(P2)三个活动等级theHarvester/lib/source_catalog.py。例如_spec(mynewsource, ResultRoute.SUBDOMAINS, ResultRoute.EMAILS, activityActivityClass.PASSIVE),目录还支撑resolve_sources()的能力选择器all会展开为全部 PASSIVE 源subdomains/emails等能力关键字会按capabilities展开theHarvester/lib/source_catalog.py。theHarvester/lib/source_runner.py在SOURCE_FACTORIES字典中新增一条工厂条目用SourceRequest构造适配器实例例如mynewsource: lambda request: mynewsource.SearchMyNewSource(request.target, request.limit),create_source()theHarvester/lib/source_runner.py通过目录中的规范名称查表构造适配器_ROUTE_GETTERStheHarvester/lib/source_runner.py把ResultRoute映射到具体的 getter 名如SUBDOMAINS → get_hostnames运行器随后收集所有声明的结果路由并随完成的 run 一起持久化。对于特殊源如 builtwith 的框架/语言/服务器/CMS/分析栈 getter、hudsonrock 的 infostealer、shodan 的 host 证据运行器还有专门的_collect_observations分支theHarvester/lib/source_runner.py。最后一条纪律保持公开的源标识符稳定且在目录、工厂、CLI、README 矩阵等所有位置使用完全相同的拼写大小写敏感的源名如securityTrails会被get_source_spec()做 casefold 归一化处理但规范名本身要一致。4. 第四步按需添加凭据如果源接受 API Key按以下四步接入项目统一的凭据体系在 theHarvester/data/api-keys.yaml 中添加空的凭据字段。文件顶层是apikeys:映射每个 Provider 一个二级映射字段按需定义。多字段示例censys需要tokenorganization_idfofa需要keyemailtomba需要keysecret单字段示例shodan、virustotal都只需要key。在Core._API_KEY_FIELDS注册这些字段theHarvester/lib/core.py。该ClassVar字典把 Provider 名映射到字段元组Core.api_key_fields()与Core._api_key_value()都依赖它。添加匹配的Core访问器供适配器使用。例如Core.shodan_key()内部调用_api_key_value(shodan)返回单个值Core.fofa_key()返回(key, email)二元组Core.censys_key()则是从api_keys()中安全取token与organization_idtheHarvester/lib/core.py。缺少必需凭据时要清晰失败。运行器会捕获MissingKeyError并把该源标记为skipped/missing-credentials不会中断整个采集见 theHarvester/lib/source_runner.py如果 key 是可选的则保留文档中说明的无 key 行为如 baidu 不要求凭据即可匿名搜索。安全红线绝不在日志中打印凭据绝不在测试、示例、提交、Issue 或 Pull Request 中包含真实 key。仓库的api-keys.yaml只存放空字段占位用户运行时会自动复制到主目录配置路径Core._read_config会按配置目录查找找不到则从数据目录创建默认文件。5. 第五步添加聚焦的测试覆盖文档推荐以 tests/discovery/test_baidusearch.py 为模板——它是一个小而完整的示例可用pytest的monkeypatch替换网络获取层包括伪造 Playwright API、伪造AsyncFetcher.open_session/fetch、伪造asyncio.sleep并断言归一化后的结果。pytestmark pytest.mark.provider_contract(baidu)把它挂入 Provider 契约测试组。需要覆盖的典型用例对应 baidu 测试中的具体测试成功解析如test_process_queries_site_first_and_reuses_one_browser断言三页 URL 顺序、domcontentloaded等待策略、60 秒超时、headlessTrue启动参数、代理注入、每页间 1.0 秒延迟以及邮箱/主机名解析结果缺少必需凭据断言抛出/记录MissingKeyError最终状态为skipped非成功响应 / 超时 / 空响应 / 畸形响应如test_http_429_is_reportedrate-limitedhttp-429、test_empty_response_is_reportedfailedno-response、test_http_fallback_reports_malformed_responsefailedinvalid-response。注意项目在 theHarvester/discovery/provider_response.py 提供了共享的provider_http_error()分类器非FetcherResponse→transport-error401/403 →access-denied429 →rate-limitedhttp-429其余非 2xx →http-{status}可直接复用分页与终止如test_unlimited_stops_when_provider_repeats_a_page验证无 limit 时遇到重复页返回partialrepeated-page并停止执行报告不完整工作返回SourceExecutionReport正常完成返回None归一化与去重如test_later_captcha_preserves_partial_results验证部分结果被保留。硬性约束测试不得依赖外部网络访问不得使用真实 Provider 凭据。所有网络行为都必须被 mock 或 stub 替换。6. 第六步更新操作员文档在仓库根目录 README.md 的 Source 矩阵中新增该源注明其结果路由routes、活动类别activity class与凭据要求。README 矩阵契约测试会逐项对照目录条目校验这些值是否一致相关契约测试见 tests/lib/test_source_catalog.py 与 tests/test_readme.py。在 Pull Request 描述中链接 Provider 官方 API 文档并解释任何对共享传输行为的有意例外例如某源必须禁用重定向、必须使用特定 UA、必须用无头浏览器而非普通 HTTP 等。7. 自检清单与工作流全景把以上六步串起来一次完整的新增发现模块工作流是阅读 CONTRIBUTING.md检查 Issues/PR 避免重复劳动确认 Provider 契约认证、限流、分页、稳定字段、最小请求序列在 theHarvester/discovery/ 实现适配器初始化器 process() 按需 getter严格遵守SourceExecutionReport状态契约与会话生命周期纪律在 theHarvester/lib/source_catalog.py 加目录条目、在 theHarvester/lib/source_runner.py 加工厂条目保证标识符拼写一致按需在 theHarvester/data/api-keys.yaml 与Core._API_KEY_FIELDS注册凭据字段并添加访问器参考 tests/discovery/test_baidusearch.py 编写全离线的聚焦测试更新 README.md 矩阵在 PR 中链接 Provider 文档并说明传输行为例外。运行新源时用户通过 CLI 指定源名如python theHarvester/theHarvester.py -d example.com -s mynewsource或使用all/ 能力选择器运行器会按目录条目校验、构造适配器、执行会话、收集并归一化证据、以SourceExecution记录执行状态completed/partial/failed/rate-limited/skipped持久化到 run 中——这正是目录驱动、运行器拥有最终状态这一架构设计的完整闭环。【免费下载链接】theHarvesterE-mails, subdomains and names Harvester - OSINT项目地址: https://gitcode.com/GitHub_Trending/th/theHarvester创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考