jhawlwut/apparitor

GitHub: jhawlwut/apparitor

apparitor 为 AI Agent 的工具调用、MCP 请求和 Agent 间通信提供统一的授权拦截层,将访问决策委托给组织已有的策略引擎(OpenFGA/Cedar/OPA),填补内容安全扫描无法覆盖的权限校验缺口。

Stars: 0 | Forks: 0

# apparitor [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/e0900a9c4a024647.svg)](https://github.com/jhawlwut/apparitor/actions/workflows/ci.yml) [![pip-audit](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/093e8569ca024653.svg)](https://github.com/jhawlwut/apparitor/actions/workflows/pip-audit.yml) [![Coverage](https://img.shields.io/badge/core%20coverage-%E2%89%A590%25-brightgreen.svg)](https://github.com/jhawlwut/apparitor/actions/workflows/ci.yml) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml) **你的 agent 绕过了你现有的授权机制。** apparitor 将它们重新 纳入管控。每个 agent 操作(一次 LLM 工具调用、一个 MCP 请求,或一次 agent 间的 调用)都会在执行 *之前*,与你已信任的策略引擎(OpenFGA、Cedar、OPA)进行核对。 它回答了内容安全层从未提出的问题:这个 agent *被允许* 执行此操作吗?采用供应商中立原则,基于 AuthZEN 1.0 互操作性标准构建, Apache-2.0。 ## 差距 你技术栈中的每个安全层都在检查 agent 操作的 *内容*:提示词 是否越狱,生成的代码是否恶意。但没有一个提出你的安全 模型实际依赖的问题。这个 agent 现在 **被允许** 为该用户、针对 该资源执行此操作吗? 最关键的操作往往是那些看起来无害的操作。一个 agent 读取客户 记录是良性的文本;而读取 *另一个租户的* 记录就是违规。没有任何内容扫描器 能区分它们。区别不在于文本,而在于谁在操作以及他们拥有什么 权限。 ``` Agent for alice@acme → read_records(tenant="globex") │ ▼ Safety scanning → "Is this prompt malicious?" → PASS (benign request) │ ▼ ??? nothing ??? → "May alice@acme read globex's records?" → NO CHECK │ ▼ Tool executes. Cross-tenant data returned. ``` 这个缺失的环节就是授权决策,而且你几乎可以肯定正在运行一个 已经做出这些决策的引擎。它只是没有被连接到 agent 实际操作的地方。apparitor 就是这种连接:它将每个 agent 操作路由到策略决策点(PDP),并将 裁决结果映射到执行点的 `ALLOW` / `BLOCK` / `HUMAN_IN_THE_LOOP` 模型上。 ``` Agent for alice@acme → read_records(tenant="globex") │ ▼ Safety scanning (PromptGuard, AlignmentCheck, CodeShield, …) → PASS │ ▼ apparitor ──────────POST /access/v1/evaluation──────▶ Policy engine (OpenFGA / Cedar / OPA / …) │ │ │ ◀────────────────── { "decision": false } ────────┘ ▼ BLOCK: "alice@acme is not authorized to call read_records for tenant globex" ``` ## 为什么不自己写检查逻辑呢? 最简单的版本,`if allowed: run()`,在四个方面存在安全漏洞,而这正是 apparitor 旨在 解决的问题: - **主体是一个 [confused-deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem) 陷阱。** 防火墙层看到的是模型输出,而不是经过身份验证的主体。如果从 工具调用中推断 *谁在操作*,agent 就可以指定自己的特权主体。 apparitor 从宿主环境中获取主体,限定在请求范围内(在 MCP 边界,从 *已验证的* OAuth token 获取),绝不从模型输出获取。参见 [身份标识](#identity-who-the-agent-acts-for)。 - **默认故障是 fail-open。** PDP 超时、`5xx` 错误、缺失或非布尔的 `decision`、无法解析的调用:每一个都会导致 `allowed` 为假值,从而被你的 `if` 语句放行。 apparitor 将每一种情况都解决为 BLOCK 或人工审查;不存在 fail-open 选项。参见 [默认 Fail-closed](#fail-closed-by-default)。 - **你需要写四次。** 检查应该位于防火墙、MCP server 以及 agent 间的边界,每一处都有不同的对象和不同的身份来源。 apparitor 是四个适配器背后的单一引擎。 - **agent 应该比其用户受到更多限制。** 为特权用户操作的越狱 agent 绝不能借用该用户的权限。apparitor 可以评估用户的授权 *以及* agent 自身的权限边界,仅在两者都允许时才继续执行。 这是一项经过单独审计的控制措施,适用于所有引擎。参见 [Level 2](#identity-who-the-agent-acts-for)。 而且你不需要编写任何新策略:它保留在你组织已经编写策略的引擎中, 与你其他授权逻辑所在的位置一起接受审计。 **四个执行点,一个引擎。** 检查运行在技术栈允许你 拦截操作的任何地方:在 agentic 防火墙内(作为 [LlamaFirewall](https://github.com/meta-llama/PurpleLlama/tree/main/LlamaFirewall) 扫描器或 [NeMo Guardrails](https://github.com/NVIDIA/NeMo-Guardrails) 轨道),在 MCP 边界作为 FastMCP server middleware,或在 agent 间边界作为 A2A executor。相同的引擎,到处都是相同的 fail-closed 语义;只有边界不同。 **一次集成,多个策略引擎。** apparitor 采用 [AuthZEN 1.0](https://openid.net/specs/authorization-interop-spec-1_0.html) 互操作性 标准,因此相同的连接可以触及你已经在编写策略的引擎: **OpenFGA**(Zanzibar / ReBAC,实验性)、**Cedar**(policy-as-code)和 **OPA / Rego**,无需重写策略。OPA 和 Cedar 也有跳过 AuthZEN 环节的原生后端。 ## 安装说明 apparitor 尚未在 PyPI 上发布;请从源码安装,并固定到某个发布标签: ``` pip install "apparitor @ git+https://github.com/jhawlwut/apparitor@v0.1.1" ``` 每个执行点和进程内的 Cedar 后端都是可选的附加项 (`[llamafirewall]`、`[nemo]`、`[fastmcp]`、`[a2a]`、`[cedar]`)。`[llamafirewall]` 会拉取 torch / ML 技术栈;纯安装和所有其他附加项都不会。有关完整的矩阵和每个附加项的安装 命令,请参见 [docs/setup.md](docs/setup.md#installation)。 ## 快速入门 选择你技术栈中已有的执行点;它们背后的引擎是相同的。 **在 LlamaFirewall 内部:** 将扫描器指向任何兼容 AuthZEN 的策略决策 点(PDP)并将其绑定到助手角色,以便它在工具调用被分发之前对其进行 拦截。OpenAI、Anthropic 和 LangChain 格式的工具调用会被自动检测和 标准化;未识别的格式将被拦截(fail closed): ``` from llamafirewall import LlamaFirewall, Role from apparitor import AuthZENScanner, ScannerConfig # 指向任何兼容 AuthZEN 的 PDP。安全默认设置:fail-closed,TLS-verified。 # Subject 必须是可解析的:设置 config.agent_id,或针对每个请求设置 current_subject。 scanner = AuthZENScanner(config=ScannerConfig(pdp_url="https://pdp.internal", agent_id="travel-bot")) firewall = LlamaFirewall(scanners={Role.ASSISTANT: [scanner]}) result = await firewall.scan_async(assistant_message) # ALLOW / BLOCK / HUMAN_IN_THE_LOOP ``` 每次请求时,提供 agent 实际代理的真实最终用户(推荐使用,而不是静态的 `agent_id`)。参见 [身份标识:agent 代理的对象](#identity-who-the-agent-acts-for)。 相同的 `AuthorizationEngine` 运行在其他三个执行点之后;只是 边界和身份来源不同: - **NeMo Guardrails 轨道** — `pip install "apparitor[nemo]"`, `NeMoAuthorizationRails`。 注册为自定义操作;该轨道拒绝被拒绝的工具调用,在 NeMo 的映射下遵循 fail-closed。在 [`examples/three-peps/`](examples/three-peps/) 中有演练。 - **FastMCP server middleware** — `pip install "apparitor[fastmcp]"`, `FastMCPAuthorizationMiddleware`。在工具运行之前在服务器端拦截 `tools/call`、`resources/read` 和 `prompts/get` ;主体是 *已验证的* OAuth `sub`,绝不是 宿主断言。请在你的 auth middleware **之后** 注册它。在 [`examples/gateway/`](examples/gateway/) 中有可用的代理示例。 - **A2A agent executor** — `pip install "apparitor[a2a]"`, `A2AAuthorizationExecutor`。 拦截每个 agent 间的 `agent.invoke`;主体是服务器已验证的 对等方。 每个适配器都有更多选项(列表过滤、双主体边界、针对 hook 的选择退出),记录在其模块的 docstring 中;有关 每个引擎的连接方式,请参见 [docs/setup.md](docs/setup.md)。AuthZEN 客户端和模型是 **无适配器** 的,可以独立使用 — `from apparitor.models import EvaluationRequest` 不需要任何防火墙依赖。 ## 身份标识:agent 代理的对象 每个决策都需要一个 **主体:** 你的策略所针对的 principal。apparitor 绝不从模型或工具输出中推断它(那将是一个 [confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem));**宿主** 在 请求范围内提供它。这里有一个包含三个级别的成熟度阶梯: - **Level 0 — 静态 agent 身份。** 设置 `agent_id`;每次调用都作为该 agent 进行授权。足以应对不依赖于最终用户的策略(*"任何 agent 都不得调用 破坏性工具"*)。 - **Level 1 — 每次请求的真实最终用户(推荐)。** 使用 `subject_scope(Subject(...))` 为 agent 运行绑定经过验证的用户;它会在退出时重置,因此主体不会 泄漏到重用相同任务/事件循环的后续请求中。 - **Level 2 — agentic 权限边界(用户 ∧ agent)。** `DualPrincipalMapper` 会评估用户的授权 *以及* agent 自身的边界,仅在两者都允许时才 继续执行,因此越狱的 agent 绝不能借用其用户的权限。 如果没有可解析的主体,扫描将以 **closed** 状态失败。带有自身 *已验证* 身份的执行点 会填充相同的接缝:FastMCP middleware 读取 已验证 OAuth token 的 `sub`,并且它的优先级高于任何宿主断言的主体。有关 完整的解析顺序、带有代码的三个级别以及双主体连接方式,请参见 [docs/setup.md](docs/setup.md#identity-resolving-the-subject)。 ## 默认 Fail-closed 每一条无法产生明确 ALLOW 的路径都会被拒绝:无法访问或超时的 PDP、 格式错误的响应(缺失或非布尔的 `decision` 是一个错误,绝不会强制转换为 allow)、缺失的主体、无法解析的工具调用。不存在 fail-open 选项;PDP 故障根据 `on_error` 解决为 `deny`(默认)或 `human_review`。PDP URL 必须 是 HTTPS 并通过 SSRF 防护,需验证 TLS 且绝不遵循重定向(唯一的退出选项 是显式的 `allow_insecure_pdp` 标志,用于本地开发)。`review_predicate` 只能 将决策 *升级* 为 `HUMAN_IN_THE_LOOP`,绝不降级。决策缓存默认 关闭,启用时仅缓存 ALLOW,以完整请求 元组的摘要作为键,并带有严格的 TTL。有关完整的故障处理和缓存设计,请参见 [docs/requirements.md](docs/requirements.md)(§3.6–3.9)。 ## 可观测性 每个决策都会被计时和计数。扫描器(以及独立的 `AuthorizationEngine`) 暴露了一个 `metrics` 接收器 — 默认是一个带有延迟 直方图和决策/缓存计数器的进程内 `InMemoryMetrics`;传入你自己的 `MetricsSink` 以转发到 Prometheus/OpenTelemetry,或传入 `NoopMetrics()` 来禁用。 ``` m = scanner.metrics m.latency_histogram() # [(le_seconds, cumulative_count), …, (+Inf, n)] m.decisions # {("allow", "success"): 12, ("block", "error"): 1} ``` 每个决策还会发出一条结构化的审计日志行(裁决结果、状态、主体 ID、 correlation id、资源 ID、参数 *指纹*);原始参数和 token 绝不会被 记录。主体 ID 是决策主体(FastMCP 下的 OAuth `sub`,可能 是一个电子邮件地址),因此请将 `apparitor` logger 视为敏感信息。日志格式是 `0.1.0` 以来的稳定性契约。请参见 [docs/audit-log.md](docs/audit-log.md) 和 [docs/requirements.md](docs/requirements.md)(§3.10)。 ## apparitor 连接的内容 **执行点**(apparitor 插入的 agent 侧 hook): | 执行点 | 供应商 | 状态 | | --- | --- | --- | | [**LlamaFirewall**](https://github.com/meta-llama/PurpleLlama/tree/main/LlamaFirewall) | Meta | 发布中 (`AuthZENScanner`) | | [**NeMo Guardrails**](https://github.com/NVIDIA/NeMo-Guardrails) | NVIDIA | 发布中 (`NeMoAuthorizationRails`) | | [**FastMCP**](https://github.com/PrefectHQ/fastmcp) server middleware | Prefect | 发布中 (`FastMCPAuthorizationMiddleware`) | | [**A2A**](https://a2a-protocol.org/) agent executor | Linux Foundation | 发布中 (`A2AAuthorizationExecutor`) | **策略引擎**(做出授权决策的地方)。apparitor 通过 AuthZEN 连接这些引擎;OPA 和 Cedar 也有跳过 AuthZEN 环节的原生后端,通过配置选择(`backend="opa"` / `backend="cedar"`): | 引擎 | 范式 | apparitor 连接方式 | 示例 | | --- | --- | --- | --- | | **Mock PDP** (测试/演示) | n/a | AuthZEN | [`examples/mock_pdp/`](examples/mock_pdp/) | | **OpenFGA** | Zanzibar / ReBAC | 原生 AuthZEN (实验性) | [`examples/openfga/`](examples/openfga/) | | **Cedar** | policy-as-code (ABAC) | AuthZEN 网关 · 原生进程内 (`backend="cedar"`) | [`examples/cedar/`](examples/cedar/) | | **OPA / Rego** | policy-as-code | AuthZEN 网关 · 原生 Data API (`backend="opa"`) | [`examples/opa/`]() | | **Amazon Verified Permissions** | 托管 Cedar | [AWS AuthZEN 接口](https://github.com/aws-samples/sample-authzen-interface-verified-permissions) | [`examples/avp/`](examples/avp/) | | 任何 AuthZEN 1.0 PDP (Cerbos, Topaz, …) | 多样 | AuthZEN | [`docs/setup.md`](docs/setup.md) | ## 文档 - [技术要求与设计决策](docs/requirements.md) - [架构](docs/architecture.md) - [设置:连接到策略引擎](docs/setup.md) - [欧盟 AI 法案 / CADA 合规参考](docs/eu-ai-act.md) - [路线图](ROADMAP.md) - [贡献指南](CONTRIBUTING.md) · [安全策略](SECURITY.md) · [更新日志](CHANGELOG.md) ## 许可证 [Apache License 2.0](LICENSE)。
标签:AI代理, MCP, 人工智能, 授权系统, 用户代理, 用户模式Hook绕过, 策略引擎, 网络安全挑战, 自定义请求头, 身份与访问控制, 逆向工具