zeroclaw-labs/zeroclaw · error · anyhow::Error
Lark/Feishu marker target resolves outside workspace_dir
Error message
Lark/Feishu marker target resolves outside workspace_dir
What it means
After canonicalizing both workspace_dir and the candidate path, validate_lark_marker_target requires candidate.starts_with(&workspace) — the file must physically live inside the workspace. Any absolute path outside the workspace, ../ traversal, or a symlink inside the workspace that points outside canonicalizes to an escaping path and bails (WARN reason=outside_workspace). This is a path-traversal containment check protecting the upload surface.
Source
Thrown at crates/zeroclaw-channels/src/lark.rs:639
.with_attrs(::serde_json::json!({"reason": "not_found"})),
"lark: marker target not found on disk"
);
anyhow::Error::msg("Lark/Feishu marker target not found on disk")
} else {
anyhow::Error::msg(format!(
"canonicalize Lark/Feishu marker target failed: {err}"
))
}
})?;
if !candidate.starts_with(&workspace) {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
.with_attrs(::serde_json::json!({"reason": "outside_workspace"})),
"lark: marker target escapes workspace_dir"
);
anyhow::bail!("Lark/Feishu marker target resolves outside workspace_dir");
}
Ok(candidate)
}
fn resolve_lark_media_marker(
marker: &LarkOutgoingMediaMarker,
workspace_dir: Option<&Path>,
) -> anyhow::Result<LarkResolvedMediaMarker> {
let path = validate_lark_marker_target(&marker.target, workspace_dir)?;
let metadata = std::fs::metadata(&path).map_err(|err| {
anyhow::Error::msg(format!(
"read Lark/Feishu marker target metadata failed: {err}"
))
})?;
if !metadata.is_file() {
anyhow::bail!("Lark/Feishu marker target is not a file");
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Make every file-producing tool write inside workspace_dir and emit workspace-relative paths in markers
- Replace symlinks inside the workspace with real files (or copies) — canonicalize follows links and the resolved target must remain inside
- If a legitimate shared asset lives outside, copy it into the workspace before attaching
Defensive patterns
Strategy: validation
Validate before calling
// Containment pre-check mirroring the channel's rule: let ws = std::fs::canonicalize(workspace_dir)?; let cand = std::fs::canonicalize(ws.join(target.trim()))?; anyhow::ensure!(cand.starts_with(&ws), "marker escapes workspace");
Try / catch
if let Err(e) = channel.send(&msg).await {
if e.to_string().contains("resolves outside workspace_dir") {
// copy the asset into the workspace, rewrite the marker target, retry
}
} Prevention
- Confine all generated artifacts to workspace_dir and emit relative paths
- Avoid symlinks inside the workspace pointing out — canonicalize defeats them by design
When it happens
Trigger: Marker target like "../../etc/passwd", "/tmp/secret.png" (absolute, outside workspace), or "assets/link.png" where link.png is a symlink to ~/private/key.png — canonicalize resolves the real location, it fails the starts_with check, and send/finalize_draft abort the upload. Pinned by lark_marker_target_rejects_workspace_escape.
Common situations: Tools that write artifacts to /tmp and reference them absolutely; macOS symlinked folders inside the workspace; agent attempting to attach dotfiles via relative traversal.
Related errors
- Lark/Feishu marker target uses a disallowed scheme
- Lark/Feishu marker target is empty
- Lark/Feishu marker target is not a file
- Slack outbound attachment path escapes workspace: {}
- attachment path {} canonicalizes to {} which escapes workspa
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/ace04b524f92398c.
Report an issue: GitHub.