BigPizzaV3/CodexPlusPlus · error · anyhow::Error
skill 中不允许包含符号链接:
Error message
skill 中不允许包含符号链接:{} What it means
While iterating zip entries, extract_skill_subtree checks file.is_symlink() and bails naming the entry. Symlink entries could point outside the destination on extraction — the same policy as codex's built-in skill-installer — so any archive containing one is refused entirely.
Solutions
- 改用不含 symlink 的 zip 包重新安装(联系仓库作者移除 symlink 或用真实文件替代)
- 若自有仓库,把 symlink 替换为复制出来的真实文件后重新打包
- 从可信来源下载 skill 包,避免安装未知第三方仓库的 skill
Defensive patterns
Strategy: try-catch
Try / catch
match state.install_from_zip(&zip_bytes, &repo_path) {
Err(e) if e.to_string().contains("符号链接") => {
eprintln!("该 skill 包含 symlink,已拒绝安装,请改用无 symlink 的包");
}
other => other?,
} Prevention
- 只安装可信来源的 skill 包
- 打包 skill 仓库时用 cp -L 等方式把 symlink 展开为真实文件
- 安装失败的 zip 不要重试,先检查包内容(unzip -l 查看类型)
When it happens
Trigger: 从 GitHub 仓库(或任意来源)下载的 zip 中包含 symlink 条目,extract_skill_subtree 或 install_from_zip 解压该包时触发。
Common situations: 仓库里作者为共享文件创建了 symlink(macOS/Linux 常见);构建产物打包时把链接原样打进 zip;恶意包注入 symlink 实施攻击。
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- 主题包路径不安全:
- 压缩包条目越界:
- zip entry escapes destination
- Dream Skin path must not be a symbolic link
- Dream Skin theme path is not a safe directory
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/4bc64f9882aa1668.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/skills.rs:788
zip_bytes: &[u8],
repo_path: &str,
destination: &Path,
) -> anyhow::Result<()> {
let repo_path = repo_path.trim_matches('/');
if repo_path.is_empty() {
anyhow::bail!("skill 在仓库中的路径不能为空");
}
let prefix = format!("{repo_path}/");
let mut archive =
zip::ZipArchive::new(Cursor::new(zip_bytes)).context("skill 仓库压缩包无法解析")?;
let mut wrote_manifest = false;
for index in 0..archive.len() {
let mut file = archive
.by_index(index)
.with_context(|| format!("读取压缩包条目 {index} 失败"))?;
if file.is_symlink() {
anyhow::bail!("skill 中不允许包含符号链接:{}", file.name());
}
let Some(relative) = crate::plugin_marketplace::zip_entry_relative_path(file.name()) else {
continue;
};
// zip 内部的分隔符恒为 '/',但上面拿回来的是 PathBuf,在 Windows 上
// to_str() 会渲染成 '\',跟用 '/' 拼出来的 prefix 永远匹配不上——结果就是
// 一个文件都解不出来,报「没有 SKILL.md」。这里统一拼回 '/' 再比。
let relative = relative
.components()
.filter_map(|component| component.as_os_str().to_str())
.collect::<Vec<_>>()
.join("/");
let Some(inner) = relative.strip_prefix(prefix.as_str()) else {
continue;
};
if inner.is_empty() {
continue;
}View on GitHub (pinned to b1ed92e5e4)