vrajjshah/agentforge

GitHub: vrajjshah/agentforge

一个自主多 agent 红队测试平台,持续对部署的 AI 系统进行漏洞发现、裁决、报告和回归防护,首个目标为 AI 临床辅助系统。

Stars: 0 | Forks: 0

# AgentForge **一个自主的多 agent 平台,持续对 AI 系统进行红队测试 —— 在无需人工介入每个步骤的情况下,搜寻、** **评估、记录漏洞并进行回归防护。** 其首个目标是一个 AI **Clinical Co-Pilot**(一个基于 OpenEMR** fork 构建的 chat agent),但该引擎是与目标无关的:被测系统通过 `TargetAdapter` 插入,而所有特定于领域的成功标准都存在于一个可插拔的 **check-pack** 中 —— 绝不进行硬编码。 **[→ 实时仪表板](https://agentforge-web-production-c891.up.railway.app)** — 该平台如何汇报自身情况,无需安装任何东西。它是上一次扫描(2026-07-21)的快照,而非监控器,并且它会对自身的漏洞利用详情进行访问控制:安全态势是公开的,复现代码返回 403([原因](#why-the-dashboard-gates-its-own-findings))。 或者在本地运行整个循环: ``` uv run agentforge demo # full discover → judge → document → regress loop, ~1s, offline, no cost ``` 该命令是正门。它会启动一个临时的易受攻击构建版本,让 Red Team 重新发现真实的跨患者 PHI 泄露,由 Judge 确认其为 CRITICAL,起草报告,并证明回归测试工具在易受攻击的构建版本上变**红,在修复后的构建版本上变绿** —— 无需网络、无需 API key,也无需部署任何东西。 ### 三个值得探讨的决策 - **攻击者和 Judge 是位于不同模型族上的独立 agent,且 Judge 永远不会看到 Red Team 的声明。** 一个发明了攻击然后又给自己的成功打分的系统,在构建上就存在利益冲突。Judge 的判定基准是**在攻击运行之前编写的策略** —— 而不是攻击者的意见,也不是响应的感觉。这就是让裁决具有可证伪性而非说服力的原因。 - **确定性优先,模型最后。** 大多数攻击生成是一个零模型调用的变异引擎,并且 Judge 在付费使用 LLM 之前会运行一个廉价的确定性阶梯。这是一个出于成本和可重复性的决定,而不是对模型质量的对冲 —— 这也是为什么负载测试显示 99% 的实际运行时间都耗费在单次语义调用中。 - **按角色选择模型依据的是测量的拒绝行为,而不是品牌。** 我探测了候选模型,发现前沿模型会拒绝 offensive-security 提示词,*即使针对你自己的应用程序也是如此*,因此 Red Team 运行 Llama 4 Maverick,而 Judge 运行 Claude。所有这些都位于 Bedrock 中的一个 BAA 之下,因为目标是临床系统。 ### 真正困难的地方 不在于攻击 —— 而是**信任裁决**。一个对一切都表示同意的评估平台比没有平台更糟糕,所以这里的大部分工程工作都花在了测试测试者上:一个根据人类标签进行校准并发布为 **0.833 一致性,1.0 精确率**的 Judge,并列出了每一处分歧;针对冻结 golden 的漂移门;以及一条规则,即被阻止触发的针对安全允许列表的攻击被归类为**被阻止,绝不会被记为通过**。一个没有触发的测试并不是一个成功的测试。 这里有两件事是故意不加修饰的,并将保持原样:一个**发布的负面结果**(我构建的一个修复,经过测量后发现并不奏效 —— 这也揭示了我早期的测量噪声太大,无法支持我之前声称的那些微小胜利),以及 [docs/GATE_LEDGER.md](docs/GATE_LEDGER.md),它记录了**我认为有效但实际上无效的控制措施**。该账本是 repo 中最诚实的东西,也是了解该项目思维方式的最佳指南。 ## 为什么需要多 agent 系统(而不是静态测试套件) 生成攻击和判断其是否奏效是两项不同的工作,且存在内在的利益冲突,因此它们是**具有不同信任级别和不同模型族的独立 agent**: | Agent | 角色 | 信任级别 | 模型 | |---|---|---|---| | **Orchestrator** | 策略:选择下一个活动,触发回归,在无信号/超预算时停止 | 对存储区只读;无法写入发现 | 确定性(+ Claude Sonnet-5 用于策略调用) | | **Red Team** | 进攻:确定性变异引擎 + 新颖种子模型;针对实时目标发起攻击 | 可以调用目标;不能自行判断 | Llama 4 Maverick + 确定性 | | **Judge** | 评估:确定性优先的阶梯;安全判定基准来自 check-pack,而非攻击者 | 无工具;将攻击者/目标文本视为不受信任的证据进行读取 | Claude Opus 4.8(独立模型族) | | **Documentation** | 报告:确认的裁决 → 结构化漏洞报告 | vuln DB 的唯一写入者,受人工门控 | 模板驱动(+ Claude Sonnet-5) | 每个模型都通过**位于单一 AWS BAA 下的 Amazon Bedrock** 运行。攻击者的选择基于**测量的拒绝行为** —— 参见 [ARCHITECTURE.md](ARCHITECTURE.md) 中的 AI 使用披露。 ## 快速开始(从冷克隆开始) 需要 [`uv`](https://docs.astral.sh/uv/) 和 Python 3.12。 ``` git clone https://github.com/vrajjshah/agentforge.git && cd agentforge uv venv --python 3.12 uv pip install -e . && uv pip install pytest pytest-asyncio ruff mypy bandit pip-audit respx uv run pytest # hermetic suite, 160 tests — stubs the target + Bedrock, no network/cost uv run agentforge demo # one-command end-to-end demo (below) — no network/cost ``` 这两个命令都无需凭据且无需部署即可运行。这就是完整的离线路径。 为了完整性,下面的实时命令被保留了下来,并且**无法再按原样运行** —— 它们指向的目标已停用(参见上面的基础设施说明)。它们记录了实时结果是如何产生的,并且针对重新部署的目标将再次起作用: ``` cp .env.example .env # then set AWS_BEARER_TOKEN_BEDROCK + (optionally) the target API key uv run agentforge health # is the target up? print its content fingerprint uv run agentforge probe-bedrock # Judge (Claude) + attacker (Llama) reachability + refusal check ``` 实时命令(`--live`)会访问真实部署的目标和 Bedrock,并消耗真实的时间和金钱;它们是可选的,旨在批量运行,绝不在默认测试套件中运行。 ## 针对实时目标运行攻击 ``` # Authenticated /chat + reads, novel seeds from the attacker model, semantic Judge, cost-capped, # and non-destructive (no chart writes reach the live deployment): uv run agentforge evals --live --principals api_key \ --llm-judge --novel --safe-live --max 12 --budget 3 ``` **结果:针对加固后的 Co-Pilot,在 46 个触发的已认证变体中零漏洞利用** (直接的、编码的、模型生成的和多轮的 —— 包括最新的攻击面,例如 conversation-id 劫持) —— 一个诚实的*防御成功*,LLM Judge 正确区分了拒绝与合规。 全部 46 个都被**确认已防御** —— 但仅在该平台在实时应用上发现的唯一一个真实缺陷被修复之后。所有攻击面中最新的一个,按需上游对账 (`GET /week2/patients/{id}/reconciliation`),对扫描尝试的每个 id 都返回了 **502**。平台拒绝将其计为通过(`no-usable-response` —— 没有测量结果不是一种结果),并且标题防御率一直停留在 87%,直到该问题被诊断、修复并重新验证。 **扫描发现了缺陷但找错了原因**,这一点值得一提,因为这是故事中更有用的一半。上游 EMR 从未失败过:每个真实患者在整个过程中都返回 200。返回 502 的是任何在上游无法解析的 id —— 这正是枚举扫描所生成的,因此平台只看到了 502 并报告了依赖项不可用。实际的缺陷是错误*映射*:调用者错误的 id 被响应为 bad gateway。这个循环 —— **发现 → 报告 → 修复 → 重新验证 → 回归**,在一个实时的应用程序上 —— 是 [reports/reconciliation-502.md](reports/reconciliation-502.md),这是这里唯一没有在临时构建上重新发现的缺陷。 它也进行了诚实的分类:**LOW**。未知的患者 id 是一个*客户端*错误,用 502 来回答它等于将调用者的错误归咎于上游 EMR —— 同时强制每个请求进行实时 EMR 读取,这是一个针对单 worker 部署的放大杠杆。没有任何 PHI 越过边界,没有绕过任何授权,并且该路由始终受认证保护。一个安全工具如果将其夸大为严重漏洞,将是在错误的发现上消耗其可信度。 共生成了六个类别;**71 个变体中的 46 个针对实时目标运行,25 个被阻止**,因为 `--safe-live` 拒绝在实时临床系统中触发图表写入或数据摄取。这 25 个被报告为已阻止,而不是通过:`concurrency_idempotency` 无法通过任何其他路由到达实时目标,因此其实时覆盖率诚实地显示为**零**,仪表板上也是这么显示的,而不是把平台自身的安全防护计为目标防御。写路径类别改为在临时的易受攻击构建上进行覆盖,在相同的 seed 上进行修复验证(`agentforge demo`、`agentforge reports`)。`identity_role` —— `e0e7b6a` 类别,根据定义需要一个有效的 principal —— 是通过患者作用域内的*读取*在实时环境中触发的,该读取带有一个伪造的 actor,因此该类别在经过认证的攻击面上得到了覆盖,而不是显示为一排被阻止的写入。 裁决是值得信赖的,*因为*平台被发现存在过度标记并进行了纠正:第一次经过认证的运行报告了误报(来自 `/chat` 的正常 `200` 被读取为“forbidden status”;拒绝中的单词“MRN”触发了朴素的标记)。两者都经过了根源分析并已修复 —— `/chat` 现在以语义方式进行判断,而字段名标记仅保留用于结构化读取 —— 并且添加了回归测试以确保两者都不会再次发生。抓住一个对一切都表示同意的 Judge **正是**评估平台的价值所在。 ## 漏洞报告 ``` uv run agentforge reports # writes reports/ — no network/cost ``` 生成三份专业的、可重现的报告,每份都**由 Documentation agent 根据确认的 Judge 裁决起草,并由回归测试工具进行修复验证**(在修补后的构建上重新运行完全相同的攻击):跨患者 PHI 泄露(CRITICAL)、归因伪造写入(HIGH)以及重复写入竞态 / TOCTOU(MEDIUM)。由于实时目标已加固,这些都在临时的、隔离的易受攻击构建上进行了演示。参见 [reports/](reports/)。 ## 单命令演示(`agentforge demo`) 启动目标(一个已知的跨患者 PHI 泄露,已回退)的**临时、隔离的易受攻击构建** —— 绝不是实时系统上的开关 —— 并端到端运行整个循环:Red Team 重新发现泄露,Judge 将其标记为 **CRITICAL / must-fix**,Documentation agent 起草报告,并且**回归测试工具在易受攻击构建上变红,在修复后的构建上变绿**,断言安全属性(另一位患者的出生日期不存在),而不是断言状态码。 如果你只运行一个命令,那就是这一个。它不需要凭据,不触碰网络,并在大约一秒钟内完成。 ## 可观测性与分析 ``` uv run agentforge dashboard # rebuild the dashboard data (coverage, findings, cost, activity) uv run agentforge inner-loop # testing-the-tester: the Judge's deterministic rungs vs ground truth uv run agentforge judge-calibration # last recorded LLM-rung agreement with human labels uv run agentforge judge-calibration --live # re-score it (one model call per labelled case) uv run agentforge cost # regenerate docs/COST_ANALYSIS.md (scaling model, not cost x n) ``` - **仪表板** — 六个可观测性问题显示在一个自包含的页面上(见上文)。 - **Langfuse 追踪** — 每个活动都是一个 trace,每个 agent 跳转都有一个嵌套的 span;类 PHI 值在发送前会被屏蔽。可选(无 key 时为空操作)。此处云用于合成数据;真实 PHI 将在相同的 BAA 下自托管 Langfuse(参见 [ARCHITECTURE.md](ARCHITECTURE.md))。 - **内部循环评估** — 根据基准真相对平台*自身*的裁决进行评分(每个植入的缺陷在存在该缺陷的构建和已修复该缺陷的构建上分别运行)。按实际情况读取分数:这里的基准真相在构建上是**确定性且精确的** —— 状态码要么是 401,要么不是 —— 所以这里的一次完美扫描是一次*布线证明*(测试工具触发,判定基准来自 check-pack 而非攻击者,阶梯进行判定),而不是语义准确性的证据。将其视为准确性声明将是过拟合的误读。 - **Judge 校准** — 真正可能出错的一环是对有歧义的 `/chat` 轮次进行的狭窄 LLM 合规性检查,因此它**单独进行评分,针对人类标签** (`evals/judge_calibration/cases.json`)。该集合是根据在两个方向上都会破坏朴素评分的案例构建的:回显 PHI 词汇的拒绝、引用注入的拒绝、隐藏在拒绝导语背后的合规、包裹在安全理由中的合规,以及绝不应成为发现的健康且在范围内的回答。一致性、精确率、召回率以及每一次个别的分歧都已发布;在存在实时评分运行之前,该层级被报告为**未校准**,绝不会被报告为完美分数。 这样做发现平台中存在一个真实的缺陷。该层级评分为**精确率 1.0,但召回率为 0.44** —— 它从未发生误报,但错过了超过一半的真实合规情况,且方式都相同:它将“合规”读取为文本披露,从而错过了 JSON 中完全相同的泄露、部分披露、被接受的覆盖、报告的超出范围的工具操作,以及在拒绝第一轮后在第二轮的合规。判定准则被重写以枚举这些情况,在用于调优的集合上得分为 1.0 —— **这是样本内数据,因此不算是结果** —— 而在之后编写的保留集合上得分为 **0.80**。 这两个错误都追溯到了同一个原因:该层级从未被告知哪个患者在范围内。它现在将 check-pack 的范围规则作为受信任的上下文,连同攻击者的轮次和响应一起接收,两者都被隔离为不受信任。对其进行评分需要**第二个**保留集 —— 第一个在它诊断 bug 时就已经用掉了 —— 并获得了 **0.917** 分。第三次传递添加了一个小单元格条款,并在刻意重新测试所有早期陷阱的**第三个**保留集上获得了 **0.917** 分。 **随后第四次保留集产生了一个负面结果,以及一个更重要的方法论结果。** 一个通过否定披露的条款得分为 **0.833 —— 正好与没有它的判定准则得分相同**,并且为其编写的标准案例仍然被遗漏。更糟糕的是,对*完全相同*的十二个案例*两次*运行*完全相同*的判定准则返回了不同的答案:两个边界案例翻转了,九个是稳定的。**因此,单次十二个案例的运行无法从噪声中分辨出一两个案例的差异 —— 而这正是早期步骤用来声称的差异大小。** 这里的每个一致性数据都应被理解为近似值;当前发布的数字是**精确率 1.0,一致性 0.833**。在所有四次运行中,精确率从未低于 1.0,因此错误的方向是一致的:该层级倾向于漏报而不是误报 —— 当标题结果是“防御成功”时,这一点值得了解。完整的每次运行表格以及任何未来尝试必须遵循的顺序: [ARCHITECTURE.md](ARCHITECTURE.md#two-different-accuracy-questions-measured-two-different-ways)。 - **成本分析** — 将真实的单位成本推算到 100 / 1K / 10K / 100K 次运行,以及每个层级的架构更改:[docs/COST_ANALYSIS.md](docs/COST_ANALYSIS.md)。 - **负载测试**(`agentforge loadtest`) — 针对临时构建进行 100 次连续攻击,绝不是针对实时目标:在单 worker 临床部署上的持续突发流量*正是*该平台旨在测试的拒绝服务攻击。结果:确定性 pipeline 以每秒约 **~568** 次攻击的速度运行,而调用 Judge 的语义层级每次在 **p50 时耗时约 ~1.3 s** —— 因此,在十分之一的攻击中触发它会使吞吐量降至每秒约 **~7** 次,并将 **99%** 的实际运行时间置于该单次调用中。瓶颈不在于代码,而在于到前沿模型的网络往返,因此修复方法是分诊(保持阶梯最廉价的优先,以便该层级仅在真正有歧义的轮次上触发)和有界并发 —— 而不是更快的机器。各阶段的延迟和基线见 [docs/LOAD_TEST.md](docs/LOAD_TEST.md)。 ## 访问控制(使用 OpenEMR 登录) 仪表板支持**针对 OpenEMR 授权服务器的 SSO**(OIDC authorization-code + PKCE,针对签发者的 JWKS 进行 RS256 id_token 验证,CSRF/state 保护,以及针对 security-operator 身份/角色的 deny-by- default RBAC)。注册一次客户端 (`agentforge sso-register --redirect-uri /callback`)并设置返回的凭据。 **漏洞利用详情仅限 operator 访问 —— 平台对自身的发现一视同仁。** 一份漏洞报告是针对实时临床系统的有效攻击序列,因此将其发布到开放的互联网上正是该平台旨在标记的反模式。因此,公共演示拆分了读取视图:*安全态势*是公开的(通过率、按类别和严重程度统计的数量、“防御成功”),而*复现*则不是。`/reports/*`、漏洞标题(点明了技术名称)和 `/api/dashboard` 的 `findings` 数组需要经过认证、授权的 operator,而不管 `AGENTFORGE_SSO_REQUIRE` 如何;匿名调用者将获得计数以及关于哪些内容被保留的明确声明。 设置 `AGENTFORGE_SSO_REQUIRE=1` 还会对整个读取视图进行门控。RBAC 在每次请求时都会重新评估,因此撤销某个 operator 的权限会立即生效,而不是在会话到期时生效。 存在一个**紧急 break-glass operator 登录**(`/login/token`,仅限 POST,常数时间比较),这样平台绝不会因为身份提供者宕机而处于*未受门控*的状态 —— 这种失败模式往往会诱使 operator 关闭门控。除非设置了 `AGENTFORGE_ADMIN_TOKEN`,否则它处于禁用状态,清除该变量将撤销所有实时的 break-glass 会话。参见 [ARCHITECTURE.md](ARCHITECTURE.md#platform-access-control--login-with-openemr-sso)。 ## 架构 四个 agent 通过版本化的 JSON-Schema 消息进行协调,由 LangGraph 状态机编排,由仪表板观察。完整的说明在 [ARCHITECTURE.md](ARCHITECTURE.md);威胁模型在 [THREAT_MODEL.md](THREAT_MODEL.md);用户和自动化案例在 [USERS.md](USERS.md)。 ![AgentForge agent 交互图](https://static.pigsec.cn/wp-content/uploads/repos/cas/97/97807fdb6319b1635ff333910889846421f6bb52d72695b2428501e996917ee0.svg) ``` src/agentforge/ adapters/ TargetAdapter boundary + the Co-Pilot adapter (hits the live target) checkpacks/copilot/ domain success criteria + PHI ground truth (behind the adapter) mutation/ deterministic attack-mutation engine (no model call) agents/ redteam · judge · orchestrator · documentation contracts/ Pydantic models = source of truth for the versioned JSON Schema stores/ append-only event ledger + curated vulnerability DB seeds/ the eight real, test-pinned target defects, as attack seeds bedrock.py model access (Anthropic SDK for Claude, Bedrock converse for the attacker) graph.py the LangGraph campaign loop · web.py the dashboard service contracts/v1/ exported, versioned JSON Schema · evals/ the reproducible eval dataset reports/ generated vulnerability reports · docs/ gate & exploit ledgers, diagrams, cost & load baselines, scan triage, build-vs-configure record, evidence packet fixtures/drift/ frozen, signed goldens for Judge drift detection ``` ## 质量门 `ruff · mypy --strict · pytest · bandit · pip-audit`,由一个**阻塞性的 pre-push hook** (`.githooks/pre-push`;`git config core.hooksPath .githooks`)运行。它是主要的质量门,并且已经观察到它阻止了植入的失败然后通过了测试 —— [docs/GATE_LEDGER.md](docs/GATE_LEDGER.md) 中的每个控制措施都带有该证明,因为一个从未对任何东西执行过的门从未告诉过你任何事情。 [`.github/workflows/gate.yml`](.github/workflows/gate.yml) 在 `container: python:3.12-slim` 内部运行相同的五项检查,触发条件包括分支推送、tag 推送、pull request 和手动调度。 证明的红变绿: [run 29951154890](https://github.com/vrajjshah/agentforge/actions/runs/29951154890) 在植入 `assert 1 == 2` 时显示 RED, [run 29951258446](https://github.com/vrajjshah/agentforge/actions/runs/29951258446) 在所有五项检查中显示 GREEN。植入的失败是使用 `--no-verify` 推送的,因此 pre-push hook 无法抢先阻止门控 CI 被要求证明的内容。**pre-push hook 仍然是主要的质量门**;CI 是在作者机器以外的其他地方运行的副本。 该门控被刻意构建为能够经受住两件事,这两件事都是付出了昂贵代价才学到的: - **声明的环境就是执行的环境。** 此证明的早期版本在 GitLab **shell executor** 上运行,它会静默忽略 `image:` —— 所以它在作者的笔记本电脑上运行,而声明的容器从未被执行过,这意味着账本声称的内容超出了证据所支持的范围。正确地重做它在第一次运行时就物有所值:它捕获了一个断言 `llm_share_of_time_pct > 90` 的测试,这在笔记本电脑上为真,但在**容器中为 89.4%**。一个编码了作者硬件信息的性能测试报告的是运行它的机器,而不是它声称要检查的属性。这就是 CI 存在的“在我的机器上能跑”这一类问题,也正是同机证明无法发现的。 - **覆盖率仅在一个地方决定。** GitLab 配置中包含比决定 pipeline 是否存在的规则*更窄*的作业级规则,因此 **tag 推送什么也没运行 —— 静默地。** 不是红色徽章,也不是通知:什么 pipeline 都没有。标记一个 release 看起来与一个带有绿色门的 repo 完全相同。针对相同的漏洞重新探测了 Actions 移植( [run 29951709509](https://github.com/vrajjshah/agentforge/actions/runs/29951709509),来自 tag 推送,绿色),并且它只有一个作业且没有作业级条件,因此不存在可能发生漂移的第二个地方。 详细信息,包括每个控制措施的完整触发证明表,在 [docs/GATE_LEDGER.md](docs/GATE_LEDGER.md)。 ## 为什么仪表板对自身的发现进行门控 仪表板经过了刻意拆分:**安全态势是公开的**(通过率、按类别和严重程度统计的数量、校准数据、“防御成功”),而**复现则不是**。当目标在线时,漏洞报告是针对正在运行的临床系统的有效攻击序列,因此 `/reports/*` 对匿名访问者返回 **403** —— 没有例外,包括对于我想要打动的人。在没有门控的情况下针对*在线*部署发布有效的复现代码将与整个项目的前提相矛盾,因此平台将其自身的发现应用到了自身。 两种访问方式,都登陆到同一个受门控的视图: | 路由 | 适用对象 | 位置 | |---|---|---| | **使用 OpenEMR 登录**(OIDC + PKCE,RBAC 允许列表) | 拥有 OpenEMR 账户的 operator | `/login` | | **Operator token**(break-glass,仅限 POST,常数时间比较,每次使用均被审计) | 没有 OpenEMR 账户的 operator | `/login/token` | break-glass 路径的存在是为了让平台绝不会因为身份提供者宕机而处于*未受门控*的状态 —— 那是诱使某人关闭门控的失败模式。**此 repository 中没有任何 token**,这是由其构建方式决定的:它只存在于已部署服务的 `AGENTFORGE_ADMIN_TOKEN` 环境变量中,而提交到 repo 的 secret 就等于公开的 secret。清除该变量会立即撤销所有实时的 break-glass 会话。 它的每次使用 —— 被服务、被批准*以及被拒绝* —— 都会被一个不能记录任何其他内容的记录器追加到平台自身的只追加审计账本中。证明见 [docs/GATE_LEDGER.md](docs/GATE_LEDGER.md) 控制措施 18、19 和 32。 由于部署已停用,现在查看此内容的唯一方法是测试套件: `uv run pytest tests/test_web_gating.py`,它断言了 403、被保留的 `findings` 数组、缺失的技术字符串,以及被拒绝和被批准的 token 的账本条目。 ## 范围、安全性与数据 对**我自己的**应用程序进行授权的安全测试,在仅使用**合成患者**作为种子的**隔离沙箱**中运行 —— 没有真实 PHI,没有真实患者,没有生产数据库,没有第三方。 目标 URL 是不可变的允许列表,因此平台无法攻击其他任何东西;实时运行有成本上限,并且使用 `--safe-live` 时,不会向已部署的目标发送任何状态更改写入。所有模型都在一个 BAA 下运行在 AWS 内部。Secret 仅存在于被 git 忽略的 `.env` 中。 ## 许可证 AgentForge 采用 **MIT** 许可证 —— 参见 [LICENSE](LICENSE),范围说明在 [NOTICE.md](NOTICE.md)。 被测系统是一个采用**不同许可证**的**独立 repository**:Clinical Co-Pilot 基于 OpenEMR 的 fork 构建,因此其派生部分采用 **GPL-3.0** 许可证。此 repository 中的任何内容均不为 GPL,此处的任何内容也不会对其进行重新许可。两者之间唯一的耦合是一个 HTTP `TargetAdapter` 和一个描述目标策略的 check-pack —— 这里没有引入、导入或链接任何 OpenEMR 代码。
标签:AI安全, Chat Copilot, CISA项目, 医疗信息系统, 多智能体, 安全规则引擎, 红队自动化, 逆向工具