{"record":{"id":"d5aef014a3274259","repo":"zeroclaw-labs/zeroclaw","slug":"slack-outbound-attachment-path-escapes-workspace","errorCode":null,"errorMessage":"Slack outbound attachment path escapes workspace: {}","messagePattern":"Slack outbound attachment path escapes workspace: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/slack.rs","lineNumber":1054,"sourceCode":"            anyhow::bail!(\"Slack outbound attachment path must be absolute: {target}\");\n        }\n\n        let workspace = self\n            .workspace_dir\n            .as_deref()\n            .context(\"Slack outbound local attachments require workspace_dir\")?;\n        let canonical_workspace = tokio::fs::canonicalize(workspace).await.with_context(|| {\n            format!(\n                \"failed to canonicalize Slack workspace {}\",\n                workspace.display()\n            )\n        })?;\n        let canonical_path = tokio::fs::canonicalize(path)\n            .await\n            .with_context(|| format!(\"Slack outbound attachment path not found: {target}\"))?;\n\n        if !canonical_path.starts_with(&canonical_workspace) {\n            anyhow::bail!(\n                \"Slack outbound attachment path escapes workspace: {}\",\n                canonical_path.display()\n            );\n        }\n\n        let metadata = tokio::fs::metadata(&canonical_path)\n            .await\n            .with_context(|| {\n                format!(\n                    \"failed to stat Slack outbound attachment {}\",\n                    canonical_path.display()\n                )\n            })?;\n        if !metadata.is_file() {\n            anyhow::bail!(\n                \"Slack outbound attachment target is not a file: {}\",\n                canonical_path.display()\n            );","sourceCodeStart":1036,"sourceCodeEnd":1072,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/slack.rs#L1036-L1072","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async fn attachment_stays_in_workspace(target: &str, workspace_dir: &Path) -> anyhow::Result<PathBuf> {\n    let path = Path::new(target);\n    anyhow::ensure!(path.is_absolute(), \"target must be absolute\");\n    let canon = tokio::fs::canonicalize(path).await?; // resolves .. and symlinks\n    let canon_ws = tokio::fs::canonicalize(workspace_dir).await?;\n    anyhow::ensure!(canon.starts_with(&canon_ws), \"target escapes workspace\");\n    Ok(canon)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["slack","attachment","path-traversal","security-guard","symlink","workspace"],"backgroundTag":"path-traversal-blocked","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}