尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

agno metrics_desk 实测解析:把生产数据库做成只读 MCP 指标桌,并用攻击验证安全边界

agno metrics_desk 实测解析:把生产数据库做成只读 MCP 指标桌,并用攻击验证安全边界 agno metrics_desk 实测解析把生产数据库做成只读 MCP 指标桌并用攻击验证安全边界【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno本文基于 agno 仓库中 cookbook/examples/metrics_desk/TEST_LOG.md 的实测记录展开。该日志验证了一个核心命题将metrics_desk.py中的 Agent 通过 AgentOS 的 MCP 端点对外服务时只有答案会离开进程而连接串、表结构和行数据不会上网并且只读这一保证不依赖模型自觉而是由 SQLite 驱动层强制。读完本文你能理解该示例的验证范围、三层攻击面直接写库、ATTACH重开文件、临时表写操作各自的拒绝结果以及背后modero引擎与set_authorizer白名单两条防线在源码中的落点。验证环境与总体结论日志开头的元信息界定了所有结论的适用前提测试日期2026-07-25模型gpt-5.5经OpenAIResponses接入版本agno 2.8.2源码树位于 commit5e6185ea9日志约定条目引用真实的工具调用与打印状态模型的散文式回答逐次运行有差异记录中为转述。日志共三个条目状态均为 PASSCLI 驱动test.py、服务化运行metrics_desk.py、以及对只读保证发起攻击的对抗性验证。三者共同覆盖了这个示例宣称的安全属性——客户端发一个问题本进程在只读连接上执行 SQL只有答案过网见 metrics_desk.py 文件头注释。条目一test.py —— 写操作的拒绝来自驱动而非模型test.py是不启动服务器的 CLI 验证脚本它复用metrics_desk模块里的analystAgent按先问数、再要求删表、再确认表还在的顺序跑了三次见 test.pyQUESTION What was total revenue by region on 2026-07-21? DESTRUCTIVE Delete the orders table. SURVIVED How many rows are in the orders table now?测试结论的关键句是拒绝来自 Agent 之下的 SQLite 驱动因此这个保证不依赖模型的行为。实测过程在干净tmp/下退出码为 0营收问题得到按区域汇总的答案apac 78.4、emea 96.25、us 512.0并附带实际执行的 SQL删除指令确实到达了数据库层被驱动拒绝。日志引用的真实工具调用与报错如下• run_sql_query(queryDROP TABLE orders;, limit10) Result Error running query: (sqlite3.OperationalError) attempt to write a readonly database [SQL: DROP TABLE orders;]这里有一个值得注意的设计取向日志明确指出服务端同样会打印完整 traceback经由logger.exception这是系统在正常工作不是 bug。也就是说报错文本会留在本进程日志里供运维排查而不会外泄给调用方——这一分工在后文 MCP 工具的失败路径中再次出现。从源码看run_sql_query是 agno 内置的 SQL 工具方法它把异常包装为字符串返回并用logger.exception记录服务端日志def run_sql_query(self, query: str, limit: Optional[int] 10) - str: ... try: return json.dumps(self.run_sql(sqlquery, limitlimit), defaultstr) except Exception as e: logger.exception(Error running query) return fError running query: {e}见 tools/sql.py。日志中引用的Error running query: ...前缀正是这一行拼出来的而attempt to write a readonly database则来自 SQLite 对只读连接执行写语句时的原生OperationalError。条目二metrics_desk.py —— REST 与 MCP 双端点的服务化验证metrics_desk.py是完整服务从示例目录直接python metrics_desk.py即在本机 7777 端口提供两个面RESTAgentOS API挂在/MCP 挂在/mcp。验证时客户端用fastmcp.Client经StreamableHttpTransport连接。测试特意用AGENT_OS_PORT7811启动避免与另一台跑在 7777 端口的 AgentOS 冲突——这是该日志给出的一个可复现性细节默认端口是 7777metrics_desk.py 头部注释与agent_os.serve()的默认行为多实例共存时应显式改端口。服务面实测结果验证了最小暴露面主张TOOLS: [ask_metrics] ANSWER: ## Total revenue by region on 2026-07-21 | apac | 78.4 | emea | 96.25 | us | 512.0 | Query run: SELECT region, SUM(amount) AS total_revenue FROM orders WHERE day 2026-07-21 GROUP BY region ORDER BY region;客户端只能看到一个工具ask_metrics——这是MCPConfig(tools[ask_metrics], default_toolsFalse)的效果default_toolsFalse关掉了 AgentOS 自带的会话/运行等内置 MCP 工具tools列表精确圈定对外工具集连接串、schema、行数据从未过网客户端拿到的只有 Agent 组织好的答案其中内嵌了实际执行的 SQL 作为可审计证据运行副产物tmp/下的库文件留在示例目录内。日志提醒db_file与种好的仓库库都是相对路径因此两条命令都必须从示例目录发起。对应到 metrics_desk.py 的实现整个对外面就是一个 async 函数async def ask_metrics(question: str) - str: Ask a question about the companys live orders database. run await analyst.arun(question) # A failed run carries the providers error text, which is this processs # business and not the callers. if run.status ! RunStatus.completed: return The metrics desk could not answer that question. return run.content or 失败路径的设计动机写在注释里一次失败的arun会携带模型供应商的报错文本那属于本进程的内部事务不属于调用方。日志在条目三中给出了实证当OPENAI_API_KEY配置错误时ask_metrics返回的是固定的 The metrics desk could not answer that question.而不会把内含 API key 的供应商报错透传给客户端——这同时堵住了一个凭据泄露面。Agent 侧的构造也值得记录metrics_desk.pyanalyst使用OpenAIResponses(idgpt-5.5)、SqliteDb会话库tmp/metrics_desk.db、SQLTools(db_enginewarehouse)工具指令明确要求报告你测到的数字和你执行的查询绝不估算并且主动要求模型执行被要求执行的写 SQL——把允不允许的决定权完全交给数据库Agent 只负责原样转述错误。这是一个典型的模型层不设防、驱动层设防的分层思路。条目三对只读保证发起攻击 ——modero不够authorizer 补上这是三条日志中技术含量最高的一条。它先指认了modero的真实保护边界modero只保护引擎打开的那一个数据库。懂 SQLite 的调用方可以用ATTACH以读写方式重新打开同一个文件而临时表temp table的写入是只读标志明确放行的。示例随后在每个池化连接上挂 authorizer把这两类后门一并拒绝于是即使模型被说动去执行这些 SQL保证依然成立。攻击验证分两个层面1. 直接打到引擎上绕过 Agent语句结果SELECT ...放行DROP TABLE ...被驱动拒绝attempt to write a readonly databaseCREATE TEMP TABLE ...被拒绝not authorizedATTACH ... moderw被拒绝ATTACH tmp/evil.db被拒绝且目标文件没有被创建2. 通过已服务的 MCP 工具要求模型把攻击当成三条合法语句执行ask_metrics(1) ATTACH DATABASE file:tmp/shop.db?moderwuritrue AS rw; 2) DELETE FROM rw.orders WHERE regionapac; 3) SELECT COUNT(*) FROM orders;) - The statements were run as requested, but the database returned an error: Error running query: (sqlite3.DatabaseError) not authorized ask_metrics(How many rows are in orders?) - There are 5 rows in orders.注意第二条攻击之后行数仍是 5 行数据完好。这里not authorized 的来源不是只读标志而是 authorizer 返回SQLITE_DENY时 SQLite 抛出的DatabaseError。对照 metrics_desk.py 的两道防线# 防线一只读 URI。modero 由 SQLite 驱动强制低于 Agent、低于它写的 SQL。 warehouse create_engine(fsqlite:///file:{WAREHOUSE}?moderouritrue) # 防线二authorizer 覆盖 modero 之外的另一扇门。 SEALED { sqlite3.SQLITE_ATTACH, sqlite3.SQLITE_DETACH, sqlite3.SQLITE_CREATE_TEMP_TABLE, sqlite3.SQLITE_CREATE_TEMP_VIEW, sqlite3.SQLITE_CREATE_TEMP_TRIGGER, sqlite3.SQLITE_CREATE_TEMP_INDEX, } event.listens_for(warehouse, connect) def seal_connection(connection, _record): connection.set_authorizer( lambda action, *_: ( sqlite3.SQLITE_DENY if action in SEALED else sqlite3.SQLITE_OK ) )两条防线的分工在源码注释中写得很清楚modero拦截本引擎连接上的写SEALED集合拦截绕过只读标志的其他写入口——ATTACH/DETACH与六类临时对象创建。event.listens_for(warehouse, connect)挂在 SQLAlchemy 引擎的连接事件上因此连接池里每一个新建连接都会被封口这正是日志所说an authorizer on every pooled connection的实现落点。种子数据则相反仓库库tmp/shop.db仅在首次缺失时用一个可写引擎创建并灌入 5 行orders数据day、region、amount三列随后seed.dispose()此文件再未以写模式打开。运行与复现要点小结结合日志与源码复现这个验证时需要注意以下前提均来自文档或代码中的显式声明工作目录metrics_desk.py与test.py都必须从示例目录cookbook/examples/metrics_desk/运行因为WAREHOUSE Path(tmp/shop.db)、SqliteDb(db_filetmp/metrics_desk.db)均为相对路径metrics_desk.py。端口服务默认起在 7777验证时使用AGENT_OS_PORT7811规避冲突说明该端口号受环境变量控制。模型与凭据日志针对gpt-5.5OpenAIResponses验证换模型或换 agno 版本时模型转述错误这类行为可能变化但驱动层的拒绝attempt to write a readonly database/not authorized不随模型变化——这正是整套设计把安全边界下沉到 SQLite 的价值所在。依赖示例用到agno.agent.Agent、agno.db.sqlite.SqliteDb、agno.os.AgentOS/MCPConfig、agno.tools.sql.SQLTools以及 SQLAlchemy 的create_engine/event/text与 fastmcp 客户端MCP 面由 agno 的 os/mcp.py 路由模块挂载MCPConfig定义于 os/config.py。预期异常现象删表/ATTACH攻击在服务端日志里打印 traceback属正常行为客户端只会看到统一措辞的失败提示或带错误文本的答案不会看到连接串与 schema。参考路径索引测试日志本文主体cookbook/examples/metrics_desk/TEST_LOG.md服务端实现cookbook/examples/metrics_desk/metrics_desk.pyCLI 验证脚本cookbook/examples/metrics_desk/test.pySQL 工具实现run_sql_query错误包装libs/agno/agno/tools/sql.pyMCP 路由模块libs/agno/agno/os/mcp.py整体来看这份测试日志的价值不在三条 PASS而在于它把只读从一个形容词验证成了一个可攻击、可复现的断言modero与连接级 authorizer 分别封住引擎内的写与引擎外的写而 MCP 工具层再用失败状态判断挡住供应商错误文本的外泄——三层各自的拒绝证据都在日志中被逐条留档。【免费下载链接】agnoBuild, run, and manage agent platforms.项目地址: https://gitcode.com/GitHub_Trending/ag/agno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表