【Bug已解决】Codex Desktop Markdown preview does not render local images referenced by relative paths 解决方案原始报错线索Codex Desktop Markdown preview does not render local images referenced by relative pathsMarkdown 预览器无法显示用相对路径引用的本地图片。一、现象长什么样作者写 Markdown 时用了相对路径图片![架构图](./images/arch.png)预览里却看到图片位置是裂图控制台报Failed to load resource或Not allowed to load local resource同一份文件用别的编辑器如 Typora / VS Code打开图片正常显示唯独这个预览器不行把图片改成绝对路径![x](/Users/me/doc/images/arch.png)又能显示了改成网络图![x](https://.../arch.png)也正常。 结论很明确预览器能加载绝对路径和网络资源唯独解析不了「相对路径」——问题出在「相对谁解析」。二、背景相对路径依赖「解析基址」2.1 什么是 base URLHTML / Markdown 里的相对路径images/arch.png必须有一个**基准地址base URL**才能算出绝对地址。基准来自Web 页面base href...或当前页面 URL 的目录本地文件被预览的.md文件所在目录桌面 WebViewWebView 加载的虚拟 URL如http://localhost/preview其目录是/preview而非文件真实目录。2.2 为什么桌面 WebView 特别容易踩坑很多桌面 Markdown 预览用内嵌 WebView 渲染 HTML。WebView 加载的可能是http://localhost:port/preview—— 此时相对路径基址是http://localhost:port/根本不包含你的./images/data:text/html,...—— 没有基址相对路径直接失效file:///tmp/preview.html—— 基址是/tmp/但你的 md 在~/doc/。 只要「WebView 加载地址的目录」≠「.md 文件真实目录」相对图片就裂。三、为什么相对路径图片裂了根因3.1 基址设置错误预览把 HTML 注入 WebView 时没把base hreffile:///真实/md/目录/写进head于是相对路径相对「虚拟预览地址」解析找不到文件。3.2 安全策略拦截file://WebView 默认禁止页面用file://加载本地资源防止任意网页读你磁盘。未显式放行file://访问图片就被拦成Not allowed to load local resource。3.3 路径未做 URL 编码 / 拼接错误中文目录、空格没编码成%20或拼接时多/少斜杠导致最终 URL 非法。3.4 相对计算用错 API直接用字符串拼接/./images/arch.png而非urljoin在多级../时算错。四、最小可运行复现Python 解析错误演示下面用urllib.parse.urljoin演示「基址选错→相对路径失效」from urllib.parse import urljoin # 情况 A基址是“md 文件真实目录” —— 正确 base_correct file:///Users/me/doc/ print(urljoin(base_correct, ./images/arch.png)) # file:///Users/me/doc/images/arch.png ✅ # 情况 B基址是“虚拟预览地址” —— 错误本 Bug 的根因 base_wrong http://localhost:8080/preview print(urljoin(base_wrong, ./images/arch.png)) # http://localhost:8080/images/arch.png ❌ 根本不在磁盘上 # 情况 C没有基址data: 注入 base_none data:text/html, print(urljoin(base_none, ./images/arch.png)) # data:images/arch.png ❌ 完全失效运行即看到基址一错相对路径就指向了不存在的地方。五、解决方案一注入正确的base href渲染 Markdown 成 HTML 时把.md文件目录作为 base 写进headimport os from urllib.parse import urljoin def render_markdown_html(md_path: str, html_body: str) - str: # 真实目录 - file:// URL注意末尾斜杠 dir_url file:// os.path.abspath(os.path.dirname(md_path)) / return f!DOCTYPE html html head meta charsetutf-8 base href{dir_url} /head body {html_body} /body /html if __name__ __main__: md /Users/me/doc/readme.md body img src./images/arch.png out render_markdown_html(md, body) print(out) # base hreffile:///Users/me/doc/ 让 ./images/arch.png 正确解析base href一设页面里所有相对路径图片、链接、CSS都相对 md 目录解析裂图消失。六、解决方案二用 urljoin 做安全的相对解析不依赖 base 标签如果不用 WebView 的 base而是在后端把相对路径预先解析成绝对路径再注入import os from urllib.parse import urljoin def resolve_image_src(md_dir: str, src: str) - str: 把 Markdown 里的图片 src 解析成可用的绝对地址。 if src.startswith((http://, https://, data:)): return src # 网络/内联图原样 if src.startswith(file://): return src # 已是绝对文件 if os.path.isabs(src): return file:// src if not src.startswith(file://) else src # 相对路径以 md 目录为基址解析 base file:// md_dir / return urljoin(base, src) if __name__ __main__: md_dir /Users/me/doc print(resolve_image_src(md_dir, ./images/arch.png)) # file:///Users/me/doc/images/arch.png print(resolve_image_src(md_dir, ../assets/logo.png)) # file:///Users/me/assets/logo.png 正确处理了 ../ print(resolve_image_src(md_dir, https://x.com/a.png)) # https://x.com/a.png 网络图原样urljoin自动处理./、../、多级回退比手写字符串拼接可靠得多。七、解决方案三放开 WebView 的本地文件访问安全前提下桌面 WebView 默认拦截file://。需要在「预览本地 md」这一受控场景下显式放行同时做好边界限制# 概念示例以通用 WebView 设置项表达非某个私有 API WEBVIEW_SETTINGS { allow_file_access: True, # 允许读本地文件 allow_file_access_from_file_urls: True, # 允许 file:// 页面读 file:// allow_universal_access_from_file_urls: False, # 不放开到任意源 } def is_safe_local_path(md_dir: str, target: str) - bool: 校验解析后的图片确实在 md 目录子树内防止 ../ 逃逸到系统目录。 import os real_md os.path.realpath(md_dir) real_target os.path.realpath(target) return os.path.commonpath([real_md, real_target]) real_md要点放开访问的同时用commonpath约束图片只能落在 md 目录及其子目录防止恶意 md 用../../etc/passwd之类读取系统文件。八、跨平台注意点8.1 路径分隔符Windows 是\拼file://时要转成/C:\a\b→file:///C:/a/b注意三个斜杠协议 空 authority 盘符。def to_file_url(path: str) - str: p os.path.abspath(path).replace(\\, /) if p[1] :: # Windows 盘符 C:/... return file:/// p return file:// p8.2 中文 / 空格编码file://里的非 ASCII 与空格应编码urllib.parse.quote否则部分 WebView 不认。8.3 与网络图共存http(s)://与data:内联图要保持原样第六节已处理不要试图把它们当本地文件解析。8.4 缓存图片更新后预览不刷新多半是 WebView 缓存。可给图片 URL 加?vmtime戳强制刷新。九、排查清单Markdown 相对图片裂图按下面排查先改成绝对路径能显示说明是「相对解析」问题不是图本身坏了检查预览 HTML 是否写了base href没写就是基址错看 WebView 加载地址是file://还是http://localhost还是data:基址要对应 md 目录控制台是否报Not allowed to load local resource是则需在受控下放开 file 访问相对路径用什么解析用urljoin而非字符串拼接路径编码中文 / 空格是否%20编码安全边界放开 file 访问时用commonpath限制只能读 md 子树Windows 盘符file:///C:/...三个斜杠别写错。十、小结「Markdown 预览不渲染相对路径本地图片」的根因几乎总是解析基址base URL选错或 WebView 安全策略拦截file://预览器把 HTML 注入虚拟地址相对路径却去了一个不存在的地方找图。通用修复注入正确base hreffile://md目录/让所有相对资源相对 md 目录解析后端预解析用urljoin把相对 src 算成绝对file://再注入正确处理./与../受控放开 file 访问WebView 允许file://的同时用commonpath把读取范围锁死在 md 子树内防止路径逃逸跨平台细节Windows 三个斜杠、中文空格编码、网络图原样保留。 记住相对路径永远相对某个基址预览器要渲染本地图基址就必须是「被预览文件真实所在目录」而不是「虚拟预览页地址」。这一条想通裂图问题迎刃而解。