dboudreau00/NocTORnal

GitHub: dboudreau00/NocTORnal

一套面向网络犯罪调查的全栈案例系统,以强制来源证明和社会网络分析为核心,构建可在证据披露中存活的归因图谱。

Stars: 0 | Forks: 0

# NocTORnal Screenshot 2026-07-26 174309 **用于网络犯罪调查的 HUMINT 与社交网络分析。** 构建参与者、身份、群体及其间信任关系的图谱 — 其中的每一条线都可以追溯到一份证据。 [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](LICENSE) [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/downloads/) [![Postgres 16](https://img.shields.io/badge/postgres-16%20%2B%20pgvector-336791.svg)](https://www.postgresql.org/) [![Tests](https://img.shields.io/badge/tests-1269%20passing-brightgreen.svg)](#验证安装) [![Status](https://img.shields.io/badge/status-alpha%20%C2%B7%20unaudited-orange.svg)](#状态) ![社会图谱](https://static.pigsec.cn/wp-content/uploads/repos/cas/90/90c76e8577dc4ac72152c7d817dcbefd944d47054d5e7ab4a4499741e4695e4d.png) 本 README 中的每一张截图都是内置的 TLP:CLEAR 合成 展示案例的实时渲染。其中的每个参与者、账号、域名和电话号码都是虚构的。
### 五项阻塞性问题,没有一个是软件问题 | | 已构建的内容 | 假设成立的内容(除非有人将其落实,否则并非事实) | |---|---|---| | **L1** | 一个接收攻击者提供的二进制文件的样本存储 | 存在一项与法律顾问共同制定的违禁内容政策,涵盖了保留与销毁的对比。只要提供足够多由攻击者选择的文件,最终总会包含仅*持有*就构成犯罪的材料——这是该问题领域的正常失败模式,而非假设。当前 `REJECTED` 会**销毁字节**,在要求保留的情况下这是错误的答案;存在 `reject(purge_bytes=False)`,但没有程序会自动选择它。 | | **L2** | 持有数千名无关人员数据的窃密日志接入 | 存在合法依据,了解受害者通知义务,并且保留期限是真实的。**90 天只是某人随手敲下的占位符。** | | **L3** | 驱动卧底账号进入论坛的身份保险库 | 在每个司法管辖区内操作该身份都已获得授权。无论意图如何,在多个司法管辖区内,使用以虚假身份注册的凭据访问系统都会触犯计算机滥用法。 | | **L4** | 消息级别的捕获,包括群组频道和通话录音 | 关于窃听法、单方同意与双方同意的对比,以及无关第三方内容的保留问题已经厘清。`provenance_class` 记录了捕获的*哪种类型*;它不能为其中任何一种赋予授权。 | | **L5** | 对钓鱼基础设施的网络捕获 | 获取攻击者基础设施已获授权,并且——另外——**在钓鱼页面输入任何信息(包括金丝雀凭据)均受相关条款约束。** 这可能构成未经授权的访问。如果没有书面授权参考,schema 将拒绝记录提交行为。 | 此外,还有 **八项操作员决定** 以及 **十三项来自文档或推理而非权威来源的事实陈述**——包括平台标识符映射(它会发生变化,如果映射过期,会产生*自信的错误归因*,而不是可见的报错)。参见 [`docs/16-legal-and-external.md`](docs/16-legal-and-external.md)。 ## 目录 [它是什么](#what-it-is) · [安装](#install) · [首次运行](#first-run) · [导览](#the-tour) · [工作原理](#how-it-works) · [十二项不可变规则](#the-twelve-invariants) · [技术栈](#tech-stack) · [项目结构](#project-layout) · [文档](#documentation) · [状态](#status) · [许可证](#licence) ## 它是什么 分析有组织网络犯罪的分析师将大部分时间花在一个属于社会学而非技术学的问题上:**谁信任谁,为什么?** 哪个中间人担保了哪个附属成员。双方接受哪个第三方担保。这个论坛上的哪个账号与那个频道上的账号是同一个人——以及说真的,到底有几分把握。 NocTORnal 就是用于这项工作的案例系统。在精神上,它最接近 Maltego 的 pivot 探索和 i2 Analyst's Notebook 的链接图表,并兼具 UCINET 对底层网络数学的严谨态度。它所增加的,是那些工具交由个人纪律来维持的东西:**不可跳过的来源证明。** 每个节点属性和关系都锚定在断言账本中的一行上,包含来源、Admiralty 可靠性/可信度评级和时间戳。没有任何代码路径可以在缺少这些的情况下写入图谱元素——这是由数据库 trigger 强制执行的,而不是口头约定。询问图表上的任何一个节点*“你为什么这么说?”*,你就会得到一条追溯至带有完整保管链、受 WORM 锁定的证据的链条。 这个单一的决定改变了工具的用途。链接图表是某人相信的图景。而这是一个能在证据披露中存活下来的案例档案。 **它的受众:** 构建勒索软件联盟结构或初始访问经纪业务的网络犯罪调查组;需要产出供律师阅读的归因报告的 CTI 团队;追踪“骡子”网络之上社会层面的金融犯罪调查员;以及任何在链接图表受到质疑时,无法回答*“那条线是从哪里来的?”*的人。 ## 你实际能用它做什么 ### 它为其构建的四种调查 **1 · 勒索软件联盟结构。** 你在三个论坛和一个泄露网站上拥有二十个账号。谁是同一个人?谁为谁担保,这种担保何时变成了互相指责?NocTORnal 将 `IDENTITY`(你观察到的账号)与 `PERSON`(你评估出的人类)区分开来,并通过带有置信度的可逆归因将它们连接——因此,“我们认为这五个账号是同一个操作者”是一个你可以展示推理过程的主张,并且可以在不丢失底层观察结果的情况下撤回。 **2 · 初始访问经纪。** 有趣的参与者很少是叫得最响的那个。介数中心性和 Burt 约束可以找到一旦移除就会切断两个团伙联系的经纪人;关键参与者分析随后会告诉你,移除*两个*指定的经纪人取得的效果不如移除一个,因为他们作为同两个团伙之间的桥梁存在冗余。这与“按中心性排名的前两名”是不同的答案,而这才是具有操作意义的答案。 **3 · 商业电子邮件入侵 (BEC)。** 财务团队将款项支付到了错误的账户。你掌握了邮件、钓鱼页面和一通电话记录。`Received` 链为你提供了*由你自己的中继服务器观察到*的发送者真实 IP——并划定了信任边界,因此没人会把案件错误归因到伪造的上游跳转节点。捕获记录将截图与重定向链和 TLS 证书绑定在一起。通话记录将伪造的来电显示和运营商的证明区分开来,因此银行的真实号码永远不会被标记在攻击者的节点上。 **4 · 欺诈和“骡子”网络。** 交易是简单的部分。其上的社会层面——谁招募了谁,双方接受哪种第三方担保,哪个担保人出现在无关的纠纷中——才是社会图谱的用武之地。 ### 从头到尾的一次完整会话 1. **开启案例。** 它包含法律依据、保留期限、审查日期和 TLP 分类。其中的每个元素都会继承这些基线。 2. **录入你掌握的内容。** 粘贴论坛档案,上传证据,输入供应商联系块,附加 `.eml`。提取程序会运行并提出*建议*——它不会直接改动图谱。 3. **分类筛选。** 接受、拒绝或推迟。接受操作会写入一条包含机器判断依据和 Admiralty 评级的断言,因此图谱永远不会忘记这是机器给出的建议。 4. **观察整体形态。** 选择一个投影(即哪些边类型算作社交联系),然后运行指标。拖动时间轴查看上个月人们相信的情况。 5. **验证你的解释。** 将竞争性假设放入 ACH(竞争性假设分析)矩阵中,并根据证据对它们进行评分。最终存留下来的就是结论;未能存留的假设也会作为旁证放入报告中。 6. **发布。** 按照目标涉密等级生成报告。任何高于该等级的内容都会在结构上被限制——而不是事后脱敏。出口网关会决定该文件是否可以流出,并记录下该决定(无论允许与否)。 ### 它与你现有工具的对比 | | | |---|---| | **vs. Maltego** | Maltego 更擅长从 transform 市场中进行广度优先的 OSINT pivot 探索。NocTORnal 是一个*案例系统*:它保留了披露工作所需的断言账本、监管链和分类模型。 | | **vs. i2 Analyst's Notebook** | i2 拥有成熟的图表绘制功能和数十年培养出的专业分析师。NocTORnal 将来源证明设为强制选项,并内置了 SNA 数学计算(Leiden、Burt、关键参与者分析),而不是将其留给单独的工具。 | | **vs. 一个 Neo4j 项目** | 图数据库只为你提供图谱。在这里所有困难的部分——断言层、五步访问网关、TLP 控制的出口、WORM 保管、保留策略和法律冻结——恰恰是你随后必须自己构建的部分。 | | **vs. 电子表格** | 坦白说,对于十个参与者,电子表格就足够了。但当有人在第 400 行问到“你为什么这么说?”时,它就无能为力了。 | ### 它*不是*什么 - **不是一套 OSINT 收集套件。** 它进行数据接入;它不是一个爬虫农场。目前提供了用于 RSS 的收集适配器和接入 API,其余部分被刻意留给你自己解决——参见上文的法律条款。 - **不是归因神谕。** 它没有“这是同一个人吗?”的按钮。它提供的是一个让你的推理过程变得明确且可逆的模型。 - **不是恶意软件沙箱。** 实验室会安全地保存样本并记录引爆*请求*;当前构建版本中的任何内容都不会引爆任何东西。 - **目前不能用于部署对抗真实材料。** 有五个法律决定构成了限制,再多代码也无法弥补它们。 ## 安装 支持两种安装方式。都是单条命令,且都可以安全地重复运行。 **Windows** ``` powershell -ExecutionPolicy Bypass -File .\release\install.ps1 ``` **macOS / Linux** ``` chmod +x release/install.sh && ./release/install.sh ``` `-ExecutionPolicy Bypass` 和 `chmod` 不是可选的:Windows 的默认策略是 `Restricted`,从 zip 中解压出的脚本带有 Mark-of-the-Web,而解压后的 `.sh` 没有执行权限位。 **安装程序的执行内容**,每一步都有反馈报告,而不是假设它已完成: 1. 查找 Python 3.12+,或者确切告诉你该如何获取它 2. 检查是否已安装 Docker,**引擎是否正在运行**,以及是否存在 Compose v2 3. 创建 `.venv` 并安装两个工作区包 4. 生成全新的 TOTP 密钥和接入 pepper 到 `.env.local`(模式 600)中,并且**绝不覆盖已有的密钥** 5. 启动 Postgres、Redis、MinIO 和 Mailpit,然后等待数据库真正接受连接 6. 应用全部 52 个 Alembic 迁移 7. 提议创建你的第一个账户,并**仅打印一次**带有用于扫描的二维码的密码 8. 启动 API 并打开控制台 详情与故障排除:**[`release/INSTALL.md`](release/INSTALL.md)**。 ### 前置条件 | | 最低要求 | 备注 | |---|---|---| | **Python** | 3.12 | 日常开发和测试使用的是 3.13 | | **Docker Desktop** | 包含 Compose v2 | 运行 Postgres, Redis, MinIO, Mailpit | | **RAM** | 8 GB | 四个容器约需 3 GB | | **Disk** | 5 GB | 镜像、数据库、对象存储 | | **OS** | Windows 10/11, macOS 12+, Linux | 支持并专门测试了 PowerShell 5.1 | | **GnuPG** | 可选 | 仅用于验证联系块上的 PGP 签名 | **端口:** 5432, 6379, 9000, 9001, 1025, 8025, 8000。任何端口冲突都会导致 Compose 启动失败;如果你已经在本地运行 Postgres,通常出错的会是 5432。除了可选的 YARA 规则获取外,安装后无需任何互联网访问。 ## 首次运行 安装程序会在登录页面留下它创建的账户。 ``` # 第二个 terminal 不需要 exports —— bootstrap.py 会读取 .env.local。 .venv/bin/python scripts/bootstrap.py create-user \ --email you@example.org --name "Your Name" ``` 在 Windows 上,该路径是 `.venv\Scripts\python`。然后注入下方所有截图的展示案例来源: ``` .venv/bin/python scripts/bootstrap.py demo-network \ --owner-email you@example.org --code OP-SHOWCASE-26 .venv/bin/python scripts/seed_deception_demo.py --case OP-SHOWCASE-26 ``` ### 验证安装 ``` DATABASE_URL="postgresql+psycopg://noctornal:dev_only_change_me@localhost:5432/noctornal" \ .venv/bin/python -m pytest apps/api/tests packages/ontology -q ``` 预期结果为 **1269 通过,0 跳过**。**如果没有 `DATABASE_URL`,你会看到大约 700 个跳过**——测试套件的一半在设计上受数据库限制。这是一个正确的结果,而不是安装失败。 ## 导览 ### 社会图谱 ![社会图谱](https://static.pigsec.cn/wp-content/uploads/repos/cas/90/90c76e8577dc4ac72152c7d817dcbefd944d47054d5e7ab4a4499741e4695e4d.png) 基于 `sigma.js` 的 WebGL 渲染。**投影决定了哪些边类型算作社交联系**——身份管道(`SAME_AS`, `IAS_OF`)不参与其中,否则你研究得最深度的那个身份看起来就会像是网络的中心。推断出的边渲染为**虚线**,除非投影选择将其纳入,否则不计入指标。底部的横条是世界时间:拖动它,图谱就会显示在那一日期被认可的状态。 ### 结构分析 ![结构分析](https://static.pigsec.cn/wp-content/uploads/repos/cas/88/88bf683d4a145e87af1723fa3f232b775d02bc9214ef06a79f929ef8fe215c85.png) 通过 `igraph` 的 C 核心计算介数中心性、特征向量中心性、k-core、Burt 约束和 Leiden 社区。**关键参与者分析是一个集合问题,而不是简单地按中心性排名的 top-n**:冗余地连接同一对团伙的两个经纪人,他们在一起的价值不如其中任何一个,面板会直接指出这一点,而不是将他们排名为第一和第二。 ### 证据与监管链 ![证据](https://static.pigsec.cn/wp-content/uploads/repos/cas/fe/fe101724ced7cba0dbb1cc3e2d10493f58f798aca304826a109e94bb9e79154c.png) 接入时计算 SHA-256 和 BLAKE3;MinIO 对象锁定处于 **COMPLIANCE** 模式,因此在保留期到期之前,即使是 root 凭据也无法更改证据;追加写入的哈希链监管账本会记录每一次接触,**包括读取**。 ### 竞争性假设 (ACH) ![ACH](https://static.pigsec.cn/wp-content/uploads/repos/cas/d7/d71ed3047c1bee415182ad850ab3960e5f292f1a9ca225871d5219a6c589880b.png) 假设会明确地根据证据进行评分,报告会在被采纳的假设旁边附带被**排除**的替代假设。没有包含被拒绝的竞争假设的分析路线,只能称之为断言,而不是评估。 ### 欺骗 —— 钓鱼、BEC 与 Vishing ![欺骗](https://static.pigsec.cn/wp-content/uploads/repos/cas/75/759f9b9de0f8e91f813636d6029ffe3c62a3a67345bccb9ec4128ac312b89285.png) 捕获的截图、重定向链和 TLS 证书构成**不可分割的单一证据**。包含 `Received` 链的 BEC 邮件以收件人优先的方式绘制,并标记了其**信任边界**,因为该跳转之上的所有内容都是攻击者可写的。通话记录将可伪造的来电显示与持久的运营商证明保存在独立的、分别标记的区块中——将它们混淆正是导致犯罪被归咎于攻击者随机挑选的号码的原因。每一个 URL 都经过无害化处理且不可点击。参见 [`docs/19`](docs/19-social-engineering-evidence.md)。 ### 恶意软件实验室 ![实验室](https://static.pigsec.cn/wp-content/uploads/repos/cas/bf/bfd0f8c75dbbf0b7376aca1539a42986fb869de87240ecf4bc7964f279423d49.png) 仅渲染元数据;绝不渲染字节流。样本在静态存储时被加密,且只能从**独立的源**下载。如果引爆请求需要将任何内容发送到边界之外,则需要指定明确的授权者和书面理由——这是通过数据库的 `CHECK` 约束强制执行的,而不是代码审查。 ### 频道与联系块 ![通讯](https://static.pigsec.cn/wp-content/uploads/repos/cas/f6/f60532cbb31df8b70ddfced1081500b9f631ce8f3e13320172508fd32040e7e9.png) **使用持久标识符,而非显示的名称。** Tox 索引 64 位十六进制的公钥,因为 nospam 会随意轮换;Telegram 索引数字 ID,并按 ID 空间进行命名空间隔离,因为用户名是可回收利用的。粘贴的供应商联系块会将第三方担保的标识符归因于**担保服务**,而不是供应商。 ### 生命周期与治理 ![治理](https://static.pigsec.cn/wp-content/uploads/repos/cas/23/23b3fac7237a7047c13a27f31ee435e475e74858f3ac45180306dd7e7bb00454.png) 保留计划;覆盖所有删除路径的法律冻结;比其描述对象存活更久的清除墓碑标记;显眼、双重控制且有时间限制的“破窗”紧急访问权限。 ### 实体列表 ![实体列表](https://static.pigsec.cn/wp-content/uploads/repos/cas/c3/c31318b75e5a8e92f8a7c592b9587949140cca3475bf774f38cbe2914556f7e2.png) 案例中的每一个节点及其类型、标签、选择器和 Admiralty 评级。类型颜色与社会图谱使用的颜色相同,因此这两个视图看起来是统一的整体。可按类型、标签或元素是否仍然依赖于有效的断言进行过滤。 ### 捕获与筛选 ![捕获与筛选](https://static.pigsec.cn/wp-content/uploads/repos/cas/1c/1cbcf3ea78ee03f63e63c106b0b9bdb3d97878a6f31acbf5fc3b4dd9d5da378a.png) 粘贴一段观察记录——论坛档案、供应商广告、联系块——提取程序就会提出**建议**。此处的任何操作都不会写入图谱。接受、拒绝或推迟;推迟操作会将模棱两可的项目搁置标记为 DISPUTED,而不是强迫你对目前尚不明确的事情给出非黑即白的答案。`J` 和 `K` 用于在队列中移动。 ### 通知 ![通知](https://static.pigsec.cn/wp-content/uploads/repos/cas/cc/cc5e69349cb533690412fb24c425c42651024519ed958c7b8b6b1675ca2c5378.png) 在**每次推送时**重新授权,而不仅仅是在订阅时。长期存在的订阅和案例分配具有不同的生命周期,而之前审查中的核心发现就是,一个只在订阅时检查过一次的通知中心存在严重缺陷。 ### 搜索 ![搜索](https://static.pigsec.cn/wp-content/uploads/repos/cas/10/10c394d11a57c2b0734668e6fa9de48a7f980a7255a74c8405e856d8ec832e7b.png) 受你自身的许可级别和隔离区过滤,因此过度定密(涉密等级过高)的元素是*不可见的*,而不是先被发现然后返回 403。这两列是独立加载的——其中一个加载失败不会使另一个变白屏。 ### 订阅源与接入 ![订阅源与接入](https://static.pigsec.cn/wp-content/uploads/repos/cas/c3/c391d6f267ebb5d45cced4ae5e0fc248dffe87675b5e94b953da5fb023f9de5c.png) 受监控的来源及其运行历史、身份保险库,以及**死信队列**:任何无法解析的内容都会连同其原始片段保留下来。静默丢弃就是你六个月后发现某个订阅源已经半瘫痪的方式。 ### 报告 ![报告](https://static.pigsec.cn/wp-content/uploads/repos/cas/9f/9f70de96ddf4e1690fffdf8e181e295652b2778e2c07a12ea3e186dd29872f49.png) 按照目标涉密等级生成——这种脱敏是*结构性的*,因此在任何阶段都不会读取高于该等级的内容,也无法通过在理由字段中填入名字来绕过它。发布是一个通过出口网关进行的单独操作,并且无论结果如何都会被记录。 ### 添加实体与添加关系 ![添加实体](https://static.pigsec.cn/wp-content/uploads/repos/cas/2b/2bc9eb4ba8ce7252dc8931ddb5734f96c38fa48f76084876d6788165cfc6584e.png) 如果没有来源、Admiralty 可靠性/可信度评级和依据,任何一个表单都无法完成提交。这是在输入层面强制执行的不变规则 1:这里不存在“先添加,后说明理由”的路径。 ![添加关系](https://static.pigsec.cn/wp-content/uploads/repos/cas/7e/7e685259704c983ff3b87a592c3d03f1fba3edc2ad10d9f34fb8020778923151.png) ## 工作原理 ### 流程 ``` flowchart TB subgraph collect["COLLECTION — machines"] F["Monitored forums,
channels, feeds"] --> X["Extractors"] I["Ingest API
write-only keys"] --> X U["Analyst paste,
upload, capture"] --> X end X -->|"never writes the graph"| P[("proposal queue")] X -.->|"unparseable"| DL[("dead letter
+ raw fragment")] P --> T{"Analyst triage"} T -->|reject| P T -->|accept| A subgraph model["THE MODEL — analysts"] A[("assertion ledger
source · Admiralty · time")] A -->|"trigger-enforced"| G[("graph
nodes + edges")] E[("evidence
WORM + custody")] --> A end G --> PR["Projections
which ties count"] PR --> SNA["igraph / leidenalg"] SNA --> INS["Sociogram + inspector"] G --> RPT["Report builder"] RPT --> EG{"TLP egress gate"} EG -->|"AMBER_STRICT / RED"| STOP["refused + audited"] EG -->|cleared| OUT["export · SMTP · webhook"] ``` 关键的形态在于:**机器永远只能触达建议队列。** 从提取器到图谱之间没有任何箭头。分析师的决策是唯一能将建议提升并写入模型的行为,且该决策在写入断言时会携带机器的判断依据——因此图谱永远不会忘记这是机器给出的建议。 ### 访问网关 每个限定在案例范围内的请求都会通过一次决策接受五项检查: ``` flowchart LR R["Request"] --> V{"1 · verb
role grants it?"} V -->|no| D403["403"] V --> AS{"2 · assignment
on this case?"} AS -->|no| D404["404 — not 403"] AS --> C{"3 · clearance
TLP dominates?"} C -->|no| D403 C --> K{"4 · compartments
read into all?"} K -->|no| D403 K --> S{"5 · step-up
MFA fresh?"} S -->|no| D401["401 · re-auth"] S --> OK["proceed"] ``` 有两个细节至关重要。**一个元素同时受到其自身标签和所在案例的双重保护**——一个 RED 节点可以存在于 AMBER 案例中,因此有效的标签是二者中更严格的那个,并取隔离区的并集。而且**授权决定是在揭示存在性之前做出的**:与案例毫无关系的调用者会得到与查询不存在的案例相同的 404 错误,因此状态码永远不会成为探测存在性的先知。 ### 数据模型 ``` erDiagram CASE ||--o{ NODE : contains CASE ||--o{ EDGE : contains CASE ||--o{ EVIDENCE : contains NODE ||--o{ ASSERTION : "justified by (>=1, enforced)" EDGE ||--o{ ASSERTION : "justified by (>=1, enforced)" EVIDENCE ||--o{ ASSERTION : cites EVIDENCE ||--o{ CUSTODY : "append-only" NODE ||--o{ SELECTOR : "normalised · strong or weak" PROPOSAL }o--|| NODE : "only via analyst accept" ``` `IDENTITY`(你观察到的身份)和 `PERSON`(你评估出的人类)是不同的节点类型,通过带有置信度的、可逆的 `ATTRIBUTED_TO` 边连接。`IDENTITY` 上没有 `real_name` 列,而且永远也不会有——这就是不变规则 2,并且 trigger 会拒绝跨层的 `SAME_AS`。 ## 十二项不可变规则 被视为 **Bug(即使所有测试通过,违反它们也是 Bug)**。每项规则都有一个同名的测试。 | # | 不可变规则 | 强制执行方式 | |---|---|---| | 1 | **没有什么是绝对的事实。** 每一个属性和边都能追溯到带有评级的断言 | 针对 `node` 和 `edge` 的延迟约束 trigger | | 2 | **账号不等于人。** `IDENTITY` ≠ `PERSON`,以可逆方式连接 | 拒绝跨层 `SAME_AS` 的 trigger | | 3 | **机器提议,分析师处置** | 没有从提取器直接到图谱的代码路径 | | 4 | **推断出的边保持独立** —— 使用虚线渲染,且不计入指标计算 | 投影选择加入;边类型上的 `is_social_tie` | | 5 | **历史被替代,永不被覆盖** | 对 `assertion` 没有破坏性的 `UPDATE` | | 6 | **审计日志是只追加的** | 行*和*语句级别的 trigger;拒绝 `TRUNCATE` | | 7 | **凭据永远不离开收集器** | 仅在 worker 进程中解密 | | 8 | **TLP 控制出口** | 一个统一的 `can_egress`,由所有四个出站路径调用 | | 9 | **持久标识符,而非展示的名称** | 每个类型的标准化处理;`durable_selector_type` | | 10 | **样本不渲染,不执行** | 独立来源,静态加密,`is_hostile_markup` | | 11 | **接入密钥是只写的** | 明确规定此项的 `CHECK` 约束 | | 12 | **任何数据都不会被静默丢弃** | 带有原始片段的死信队列 | ## 技术栈 ### 后端 | 层级 | 选择 | 为什么是这个,而不是显而易见的替代方案 | |---|---|---| | **记录系统** | Postgres 16 + pgvector | 图谱、断言账本和审计日志存在于**同一个事务存储**中,因此推断及其依据要么同时提交,要么同时失败。独立的图数据库会使其变成一个分布式事务问题,而这正是来源证明丢失的根源。 | | **API** | Python 3.12+ / FastAPI | 异步、强类型、原生支持 OpenAPI。 | | **SNA 数学计算** | `igraph` (C 核心) + `leidenalg` | **不使用 NetworkX** —— 纯 Python 实现,在计算介数中心性时处理大约 50k 条边就会崩溃。**使用 Leiden,而非 Louvain** —— Louvain 可能会产生内部断开的社区。 | | **对象存储** | MinIO, S3 对象锁定 | COMPLIANCE 模式保留,因此应用程序自身的凭据也无法删除证据。GOVERNANCE 模式是可以被绕过的,因此不提供 WORM 保障。 | | **缓存 / 限制** | Redis | 在一个原子 Lua 脚本中实现 GCRA 速率限制。 | | **数据库迁移** | Alembic | 52 个修订版,每次只处理一个问题,全部可逆。 | | **实时更新** | Postgres `LISTEN`/`NOTIFY` | 优先于 Redis pub/sub,因为 trigger 内部的 `pg_notify` 是**写入事务的一部分**——没有双重写入,不会丢失事件。 | ### 前端 在严格的 CSP 下使用原生 HTML、CSS 和 ES modules。**没有构建步骤,没有框架,没有 `node_modules`。** `graphology` + `sigma.js` (WebGL) 负责渲染社会图谱;web worker 负责处理布局。 这是一个刻意权衡的结果。控制台由 API 在同源下提供服务,因此没有 CORS 攻击面;这里没有 `unsafe-inline`,因此即使存在存储型 XSS,也没有脚本执行的上下文;整个 UI 只需通读代码即可完成审计。对于一个会渲染攻击者提供的字符串(论坛账号、文件名、电子邮件显示名)的工具来说,这比开发人员的便利性更重要。每一个到达 DOM 的值都会通过 `textContent` 处理,绝不作为标记语言处理,并且有测试强制执行这一点。 ### 测试 跨越两个 pytest 根目录的 **1269 个测试**。每一个不可变规则都有一个同名的测试。大约一半的测试由数据库支持,并受 `DATABASE_URL` 限制;其余的完全不需要依赖任何服务。 ## 项目结构 ``` noctornal/ ├── apps/api/ FastAPI application │ └── src/noctornal_api/ │ ├── http/routers/ one router per subsystem │ ├── http/static/ the analyst console (no build step) │ ├── security/ the five-part access gate │ ├── graph.py the only writer of nodes and edges │ ├── evidence.py WORM ingest, custody, integrity │ ├── analytics.py igraph / leidenalg │ ├── deception.py phishing, BEC, vishing (docs/19) │ └── samples.py the malware lab (docs/11) ├── packages/ontology/ THE source of node/edge/selector types │ ├── src/…/definition.py edit here, regenerate, ship a migration │ └── generated/ TypeScript + SQL seed (do not edit) ├── db/ │ ├── schema.sql annotated reference schema │ └── migrations/versions/ 52 Alembic revisions ├── docs/ 00–19, the reasoning ├── release/ installers, INSTALL, MANUAL, CHANGELOG ├── scripts/ launch, bootstrap, demo seeds, screenshots └── infra/docker-compose.yml Postgres, Redis, MinIO, Mailpit ``` ## 文档 | 阅读 | 用于 | |---|---| | **[`release/INSTALL.md`](release/INSTALL.md)** | 详细安装说明及故障排除 | | **[`release/MANUAL.md`](release/MANUAL.md)** | 操作指南 —— 每一个面板、每一次拒绝及其含义 | | [`docs/18-legal-review-pack.md`](docs/18-legal-review-pack.md) | **签署确认文档**,为每个问题提供一行答复 | | [`docs/00-decisions.md`](docs/00-decisions.md) | 解释架构为何如此设计 | | [`docs/01-domain-model.md`](docs/01-domain-model.md) | 节点、边、选择器、断言 | | [`docs/03-graph-analytics.md`](docs/03-graph-analytics.md) | SNA 方法论及其局限性 | | [`docs/05-security-rbac.md`](docs/05-security-rbac.md) | 访问控制模型 | | [`docs/19-social-engineering-evidence.md`](docs/19-social-engineering-evidence.md) | 钓鱼、BEC 与 Vishing 证据 | | [`docs/17-flagged-for-review.md`](docs/17-flagged-for-review.md) | 诚实地列出的已知缺陷 | | [`TestFlight.md`](TestFlight.md) | **实际执行安装的过程** —— 包含每一条命令、输出及其发现的三个缺陷 | | [`NOTICE.md`](NOTICE.md) | 许可证说明,以及为何必须选择该许可证 | | [`CONVENTIONS.md`](CONVENTIONS.md) | 如果你打算做出贡献,请阅读此工作协议 | ## 状态 **Alpha 阶段。未审计。未获得用于司法证据用途的认证。** 端到端可用的功能:案例;图谱与断言层;带有 WORM 和监管链的证据;五步访问网关SNA 分析;建议与筛选;实体合并;通讯与联系块;收集与接入;保留策略、法律冻结与破窗机制;ACH;带有 TLP 出口网关的报告;恶意软件实验室;欺骗子系统;实时变更推送;以及涵盖所有这些功能的分析师控制台。 刻意缺失的内容及原因见 [`docs/17`](docs/17-flagged-for-review.md): WebAuthn(目前使用密码 + TOTP)、会话 IP/UA 绑定、非所有者数据库角色下的行级安全性、Jira 集成、CONCON 块模型,以及 **任何形式的实时拦截**。 ### 具有延续意义的发现 对第 7 阶段的一项对抗性审查在完全通过(绿灯)的 953 个测试套件下发现了三个关键缺陷。它们现已修复,并且各自留下了一条准则: - **源自解析器信任了它无法控制的流的伪造判定。** 精心构造的 OpenPGP 用户 ID 利用 `str.splitlines()` 视为换行符而 gpg 未转义的字符,将伪造的 `VALIDSIG` 行偷偷带入 gpg 的状态输出中——从而为攻击者从未拥有的密钥生成了一个 CONFIRMED 身份绑定。**`CHECK` 约束无法捕获它**,因为比较的两个值都来自同一个被欺骗的解析过程。约束可以防止应用程序*忘记*检查,但永远无法防止它检查的是被伪造的输入。 *→ 在提交 `12ff904` 之前记录的任何 `CONFIRMED` 绑定都应重新推导,而不是直接信任。* - **在 Windows 开发主机上无效,在 Linux 上却是可利用的。** 该缺陷取决于字节如何解码,因此开发机器和部署目标对于系统是否可被利用得出了截然不同的结论。 *→ 当防御机制依赖于解码时,请务必对字节进行测试。* - **被夸大了 499 倍的指标。** Newman 加权除以的是过滤*后*剩余的参与者数量,因此两个共享一个 500 人频道的人得分居然和私密的双方对话一样高。 *→ 在提交 `8595602` 之前生成的任何共同参与数据都是错误的,而不是近似值。* **决定 D8 现已关闭。** 一个 Telegram 频道 ID 和一个不相关的用户 ID 可能会被标准化为相同的持久值——这是一个强选择器,因此它被反馈给了自动合并机制。Bot-API 的编码方式是算术形式的(`chat_id = -(10¹² + id)`),而不是文本前缀,而旧代码剥离了字符 `100`,这仅对十位数的频道 ID 产生了反转效果。目前的解码方式改为了算术计算,并按 ID 空间进行了命名空间隔离(`u:`/`c:`/`g:`);迁移脚本 `0051` 对已存储的选择器进行了重新键入。它无法撤销已经完成的合并,并对此做出了明示。 **该软件已经经历了八次对抗性审查。每一次审查都发现了真正的缺陷——其中有四次是关键缺陷——而且每次都是在测试完全通过的情况下发现的。其中有三个绿灯测试断言了该 Bug 的存在。请假设第九次审查也会有所发现。** ## 许可证 **[GNU Affero General Public License v3.0 or later](LICENSE).** 这不是一个自由的选择。`igraph` 采用 GPL-2.0-or-later,而 `leidenalg` 采用 GPL-3.0-or-later,两者都被分析引擎直接导入,因此对于分发的整体而言,宽松的许可证根本不在考虑范围内。在 GPL-3.0 或 AGPL-3.0 之间,对于网络服务而言,AGPL 是具有内在一致性的选择。 完整的理由说明、对内部使用的影响,以及关于第三方立场的声明:**[`NOTICE.md`](NOTICE.md)**。 在你自己的组织内部运行它不会带来任何公开义务。你的案例数据完全归你所有——许可证仅涵盖软件本身,不涉及你放入其中的任何内容。
标签:Postgres, Python, 搜索引擎查询, 无后门, 案件管理, 版权保护, 社会网络分析, 网络犯罪调查