news 2026/8/18 9:22:25

【Bug已解决】Filesystem file search rejects valid path segments containing double dots

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Bug已解决】Filesystem file search rejects valid path segments containing double dots

【Bug已解决】Filesystem file search rejects valid path segments containing double dots

一、现象长什么样

某文件系统的文件搜索工具(比如 agent 用来"在目录里找文件"的FileSystem工具)带一个防路径穿越(path traversal)的保护:拒绝任何包含..的路径,防止用户/模型读到上级目录。但这个保护过激了——它把"路径里出现..子串"一律拒绝,连合法的、非穿越的..用法也挡掉了:

  • ..开头的隐藏文件,如..bashrc(少见但合法)、..hidden
  • 文件名中间含连续两点,如report..final.mda..b.txtv1..2(用户用..当分隔符)。
  • 某些工具把..用于"相对上移"以外语义,比如版本或命名约定。

结果:用户明明要搜一个就叫report..final.md的文件,工具直接ValueError: path traversal detected拒绝,明明没有穿越意图。过度防御变成了功能缺陷。

二、背景

路径穿越防护的标准写法是:解析路径后检查是否有段等于..(即os.path.normpath之后是否跳出 base)。但偷懒的实现直接做字符串检查:

if ".." in path: raise ValueError("path traversal detected")

".." in path会匹配任何包含连续两点的字符串,包括report..final.md。真正该防的是"..作为路径段(被/包围或位于段首)",而不是"..作为子串"。

合法文件名里..完全可能出现(Unix 允许文件名包含几乎任何字符,除了/和 NUL)。所以"含..即拒绝"是错误的启发式。

三、根因

根因是把"子串包含"当成"路径段穿越"

  1. 子串匹配误判".." in path命中所有连续两点,不论其是否构成路径段。
  2. 未做路径归一化再判段:正确做法是用os.path.normpath/PurePosixPath解析,再检查每个段是否等于"..",或解析后是否仍在 base 内。
  3. 混淆"文件名里的.."与"目录里的..":前者是普通字符,后者才是穿越。

本质:用字符串启发式替代了正确的路径语义解析,把合法的命名字符当成了攻击特征。

四、最小可运行复现

下面缩略逻辑复现误拒:

def bad_safe(path): if ".." in path: # 错误:子串匹配 raise ValueError("path traversal detected") return path for p in ["report..final.md", "../etc/passwd", "a..b.txt"]: try: print(p, "->", bad_safe(p)) except ValueError as e: print(p, "-> REJECTED:", e) # report..final.md -> REJECTED (合法却被拒) # ../etc/passwd -> REJECTED (正确拒绝) # a..b.txt -> REJECTED (合法却被拒)

修复:用路径归一化,只在"段等于 .. 且导致越界"时拒绝。

from pathlib import Path def good_safe(path, base): resolved = (base / path).resolve() base_resolved = Path(base).resolve() if base_resolved not in resolved.parents and resolved != base_resolved: raise ValueError("path escapes base") return resolved

五、解决方案(第一层:最小直接修复)

最小修法:放弃子串检查,改为路径解析后判断"是否仍在 base 之内",..只有当它真的导致跳出 base 时才拒绝;文件名里的..不受影响。

from pathlib import Path def is_safe_under(path: str, base: str) -> bool: base_r = Path(base).resolve() target = (base_r / path).resolve() return base_r in target.parents or target == base_r def search_file(path, base): if not is_safe_under(path, base): raise ValueError("path escapes base directory") # 继续搜索 ...

这一层让report..final.md正常通过,../etc/passwd正确拒绝。

六、解决方案(第二层:结构化改进)

把"路径穿越判定规则"固化成策略对象,作为单一事实来源,明确按段解析而非子串。

from dataclasses import dataclass from pathlib import Path from typing import List @dataclass(frozen=True) class LangChainFilesystemSearchPolicy: """文件系统搜索路径安全策略的单一事实来源。""" forbid_substring_double_dot: bool = False # 必须为 False,否则误拒 resolve_and_contain: bool = True allow_dotfiles: bool = True def is_safe(self, path: str, base: str) -> bool: if self.forbid_substring_double_dot: raise AssertionError("substring '..' check causes false rejects") base_r = Path(base).resolve() target = (base_r / path).resolve() return self.resolve_and_contain and ( base_r in target.parents or target == base_r ) def validate(self) -> None: if self.forbid_substring_double_dot: raise AssertionError("must use segment-based check, not substring")

搜索工具用policy.is_safe,安全性由"解析后是否越界"决定,而非文件名长什么样。

七、解决方案(第三层:断言 / CI 守护)

用 pytest 锁死正确判定:

import pytest from pathlib import Path from policy import LangChainFilesystemSearchPolicy as P def test_legal_double_dot_allowed(tmp_path): p = P() (tmp_path / "report..final.md").write_text("x") assert p.is_safe("report..final.md", str(tmp_path)) is True def test_traversal_rejected(tmp_path): p = P() assert p.is_safe("../etc/passwd", str(tmp_path)) is False def test_no_substring_check(): with pytest.raises(AssertionError): P(forbid_substring_double_dot=True).validate() def test_dotfile_allowed(tmp_path): p = P() (tmp_path / "..hidden").write_text("x") assert p.is_safe("..hidden", str(tmp_path)) is True

CI 加一条:FileSystem搜索单测必须覆盖"文件名含.."的合法用例,断言不被拒;并覆盖真实穿越用例,断言被拒。

八、排查清单

  • report..final.md被拒?→ 搜索用了".." in path子串检查,误判。
  • 文件名里的..是否合法?→ Unix 文件名可含..,非穿越。
  • 是否做了路径归一化?→ 用Path.resolve()再判断是否越界 base。
  • 真穿越(../)是否被拒?→ 归一化后段为..且越界,应拒。
  • CI 是否禁止子串检查?→ 扫描".." in路径校验。
  • 隐藏文件..x是否放行?→ 应放行(合法命名)。

九、小结

文件系统文件搜索把"路径里出现..子串"一律拒绝,误伤了文件名中合法含..的情况(如report..final.md、隐藏文件..hidden),过度防御变成功能缺陷。根因是用字符串子串匹配替代了正确的路径语义解析。第一层改为路径归一化后判断是否越界 base;第二层用LangChainFilesystemSearchPolicy把判定规则固化成单一事实来源并禁止子串检查;第三层用 pytest 守护合法..文件名被放行、真实穿越被拒。路径安全的通用原则:按解析后的路径段判断是否越界,而非按文件名长相做字符串启发式

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/18 9:18:42

新能源车补贴退坡窗口期购车指南:机遇、风险与决策清单

1. 一个老车贩子的观察:补贴退坡前的窗口期 最近在店里喝茶,好几个老客户都来问同一个事儿:“听说新能源车的补贴快没了,现在是不是抄底的好时候?” 这话问得,让我想起每年年底的商场大促,最后几…

作者头像 李华
网站建设 2026/8/18 9:18:04

NCM文件解密实战指南:ncmdump免费工具三分钟解锁网易云音乐

NCM文件解密实战指南:ncmdump免费工具三分钟解锁网易云音乐 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 在网易云音乐下载的歌,落到电脑上是一个 .ncm 文件,除了官方客户端谁都不认。ncmdump 是…

作者头像 李华
网站建设 2026/8/18 9:17:17

大模型核心原理与工程实践:从Transformer到微调部署全链路解析

在实际项目中,理解大模型(Large Language Model, LLM)的内部工作机制,是进行有效应用、微调、部署乃至问题排查的基础。很多开发者虽然能调用API或运行开源模型,但对模型如何从海量文本中“学习”,又如何生…

作者头像 李华
网站建设 2026/8/18 9:11:34

企业融资策略解析:从特斯拉27亿美元融资看资本运作逻辑

1. 从一则融资新闻看企业资本运作的底层逻辑 今天早上刷新闻,看到特斯拉又搞了个大动作,完成了新一轮高达27亿美元的债券和股票融资。这消息一出,圈内圈外讨论得挺热闹。很多人可能觉得,这不就是一家公司又融到钱了吗?…

作者头像 李华