最新资讯
bloomberg.com Nvidia 加强东南亚芯片追踪以打击走私 thestack.technology 模型路由需求升温,Agent Router 推出开源替代方案 www.youtube.com Google DeepMind 的 Philipp Schmid:AI 同事需要具备的七个要素 unite.ai Cashfree 为 AI Agent 的托管结账加入 WebMCP 工具 dev.to 我们在 DolphinBench 上测试记忆检索:纯关键词搜索暴露了自身回归 dev.to 别再把 Agent 写成 while 循环,把它建模为状态机 analyticsindiamag.com 小语言模型可能重塑企业 AI 部署方式 bloomberg.com Rightmove 与 Scout24 借 AI 产品应对低迷房市并上调展望 stackoverflow.blog 欢迎来到芯片时代:半导体制造、AI 数据中心与芯片设计的未来 www.youtube.com AI Agent 总犯同样的错:CentralAgent AI 将记忆重构为经验系统 pub.towardsai.net Netflix 科学家建议重采样 LLM 评判:5 次仅将误差棒缩小 13% pub.towardsai.net Jev 作为 LLM 前的静默层:构建提示感知的模型路由器
多元动态
登录 注册
智能体 dev.to · @Mohsen Seyedkazemi Ardebili

linter 的“安全”自动修复会静默禁用 RBAC

原文 我的收藏

作者在 KubeIntellect(一个对真实集群执行 kubectl 的 AI agent)中发现,LangChain 通过 `type_ is RunnableConfig` 的同一性比较来查找需要注入的 run config 参数。若按 Ruff UP045 或 mypy 建议把注解改为 `RunnableConfig | None` 或 `Optional[RunnableConfig]`,该比较不再匹配,参数不会被注入,工具将永远收到 config=None。由于 RBAC 的 user_role 回退默认值为 admin,只读 API key 会以 admin 身份执行,权限检查代码和单测仍存在但永不触发;human-approval gate 因 hitl_bypass 默认 False 不会同样失效,反而更频繁要求审批。作者在 langchain-core 1.6.2 上以三个函数对比验证了 bare 可注入、widened 与 optional_form 均不可注入,且无报错或警告。

AI推荐理由 文章给出可复现的具体技术证据:在 langchain-core 1.6.2 上,LangChain 通过 `type_ is RunnableConfig` 的同一性比较来定位注入参数,因此把注解改成 `RunnableConfig | None` 或 `Optional[RunnableConfig]` 后注入参数变为 None,工具永远收到 config=None;作者还指出 RBAC 的 fallback 默认 admin 导致只读密钥失效,而 human-approval gate 因 hitl_bypass 默认 False 反而更频繁触发,未夸大。局限是单一作者博客、未提供完整仓库或修复方案,也未说明影响范围。

78

正文

KubeIntellect is an AI agent that runs kubectl against a live cluster, so its tools carry a role check and a human-approval gate. Both depend on one thing: the tool actually receiving the run config the graph injects. This is the parameter that receives it. config: Annotated[RunnableConfig, InjectedToolArg] = None, # type: ignore[assignment] Enter fullscreen mode Exit fullscreen mode That line is wrong in every way a reviewer is trained to notice. The default is None, but the annotation does not say | None. mypy complains, which is why there is a type: ignore sitting on it. Ruff wants to rewrite the Optional[...] spelling of it. Every instinct says clean this up. Cleaning it up disables role-based access control. Why the annotation is load-bearing LangChain finds the parameter to inject by walking the type hints and comparing with is: for name, type_ in type_hints.items(): if type_ is RunnableConfig: return name return None Enter fullscreen mode Exit fullscreen mode That is an identity comparison against one exact class object. RunnableConfig | None is a UnionType, not that object. It does not match, so no parameter is selected, so nothing is injected. Three functions, run against langchain-core 1.6.2: def bare(cmd: str, config: Annotated[RunnableConfig, InjectedToolArg] = None): ... def widened(cmd: str, config: Annotated[RunnableConfig | None, InjectedToolArg] = None): ... def optional_form(cmd: str, config: Annotated[Optional[RunnableConfig], InjectedToolArg] = None): ... Enter fullscreen mode Exit fullscreen mode bare -> injected param: 'config' widened -> injected param: None optional_form -> injected param: None Enter fullscreen mode Exit fullscreen mode No error. No warning. The tool still runs. It just receives config=None forever. What is actually lost The caller's role comes off that config, and the fallback is the problem: user_role = "admin" if config: user_role = (config.get("configurable") or {}).get("user_role", "admin") Enter fullscreen mode Exit fullscreen mode With the config gone, config is falsy and every call executes as admin. A readonly API key stops being read-only. The check that returns [Permission Denied] Your API key has read-only access is still there, still covered by its unit tests, still passing — and it never fires, because the role it compares against is no longer the caller's role. To be precise about the other half, since overstating this would be easy: the human-approval gate does not break the same way. hitl_bypass also comes off the config and defaults to False, so losing the config makes the tool prompt for approval more often, not less. The RBAC default is the one that fails open. The part that makes it a trap rather than a bug Nobody has to be careless to introduce this. The tooling volunteers it. $ ruff check --select UP045 probe.py UP045 [*] Use `X | None` for type annotations --> probe.py:5:35 help: Convert to `X | None` [*] 1 fixable with the `--fix` option. Enter fullscreen mode Exit fullscreen mode The [*] means ruff classifies that rewrite as a safe fix. Not --unsafe-fixes. Plain ruff check --fix — the command people run without reading the diff — converts a working authorization boundary into a no-op. A behavioral test will not save you either. The tools most likely to get "cleaned up" are the ones that never read config at all: they pass every test they have while silently receiving None. We found exactly that shape in four of our own read verbs. Harmless there, because those verbs make no authorization decision — but it is the same defect, one file away from a place where it matters. What we did instead of a comment There is a comment on the line. Comments do not fail CI. The guard is a test that asserts the annotation itself. It scans every config: Annotated[..., InjectedToolArg] parameter in the source and fails if any is not bare RunnableConfig. Alongside it are two dynamic canaries that build a real tool with each spelling and assert, against whatever langchain version is actually installed, that the required form is injected and the forbidden form is not — so the day the library changes its matching rule, a test says so instead of production. There is also a test asserting the scanner finds the known sites, because a regex that matched nothing would make every other assertion in the file vacuously true. The general version When a framework dispatches on type identity, your annotation is not documentation. It is runtime configuration written in the type language — and anything that "improves" your types can change behavior: a linter, a type checker, an IDE quick-fix, or an agent asked to clean up implicit Optional. If you have a line like that, the fix is not a louder comment. It is a test that fails when someone improves it. Code: https://github.com/MSKazemi/kubeintellect The guard: v4/tests/test_injected_config_invariant.py

来源报道 · 1 篇 · 以下链接均前往原始媒体

Mohsen Seyedkazemi Ardebili linter 的“安全”自动修复会静默禁用 RBAC 09-21 · 原文发布
← 返回精选动态 原文

更多内容