zeroclaw-labs/zeroclaw · error · anyhow::Error
Slack outbound attachment path escapes workspace: {}
Error message
Slack outbound attachment path escapes workspace: {} What it means
Raised by SlackChannel::resolve_outbound_attachment_marker when the canonicalized target path does not start with the canonicalized workspace_dir — a path-traversal containment guard. The target is first canonicalized (resolving .., ., and symlinks, and failing with a 'not found' context if absent), then checked against the canonical workspace prefix; anything escaping the workspace, including via symlink, is refused. This blocks both '../' traversal and symlink escapes.
Source
Thrown at crates/zeroclaw-channels/src/slack.rs:1054
anyhow::bail!("Slack outbound attachment path must be absolute: {target}");
}
let workspace = self
.workspace_dir
.as_deref()
.context("Slack outbound local attachments require workspace_dir")?;
let canonical_workspace = tokio::fs::canonicalize(workspace).await.with_context(|| {
format!(
"failed to canonicalize Slack workspace {}",
workspace.display()
)
})?;
let canonical_path = tokio::fs::canonicalize(path)
.await
.with_context(|| format!("Slack outbound attachment path not found: {target}"))?;
if !canonical_path.starts_with(&canonical_workspace) {
anyhow::bail!(
"Slack outbound attachment path escapes workspace: {}",
canonical_path.display()
);
}
let metadata = tokio::fs::metadata(&canonical_path)
.await
.with_context(|| {
format!(
"failed to stat Slack outbound attachment {}",
canonical_path.display()
)
})?;
if !metadata.is_file() {
anyhow::bail!(
"Slack outbound attachment target is not a file: {}",
canonical_path.display()
);View on GitHub (pinned to 88bb9c8533)
Solutions
- Copy or generate the file inside workspace_dir and reference that copy with an absolute path
- Sanitize agent-generated markers: reject '..' components and resolve symlinks before composing the message
- If shared assets are legitimately needed, place or symlink them into the workspace from the outside — the target path itself must canonicalize inside
Defensive patterns
Strategy: validation
Validate before calling
async fn attachment_stays_in_workspace(target: &str, workspace_dir: &Path) -> anyhow::Result<PathBuf> {
let path = Path::new(target);
anyhow::ensure!(path.is_absolute(), "target must be absolute");
let canon = tokio::fs::canonicalize(path).await?; // resolves .. and symlinks
let canon_ws = tokio::fs::canonicalize(workspace_dir).await?;
anyhow::ensure!(canon.starts_with(&canon_ws), "target escapes workspace");
Ok(canon)
} Prevention
- Reject '..' components and canonicalize agent-generated targets before composing markers
- Remember symlinks count: a link inside the workspace pointing outside fails after canonicalization
- Treat this guard as a security control — never 'fix' it by widening the workspace root
When it happens
Trigger: A marker target like [file:/workspace/../../etc/passwd], an absolute path that simply lies outside workspace_dir, or a path inside the workspace that is a symlink pointing outside — canonicalize resolves it to the real location, the starts_with check fails, and the bail fires.
Common situations: Untrusted LLM output composing attachment markers that reference system files; templates using ../ to reach shared dirs; symlinks in the workspace created by other tooling pointing to /tmp or home directories.
Related errors
- Slack outbound attachment target must be a local workspace p
- Slack outbound attachment path must be absolute: {target}
- Slack outbound attachment target is not a file: {}
- attachment path {} canonicalizes to {} which escapes workspa
- Lark/Feishu marker target resolves outside workspace_dir
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/d5aef014a3274259.
Report an issue: GitHub.