{"record":{"id":"c82f99811096e917","repo":"zed-industries/zed","slug":"sandbox-write-grant-is-a-symlink-not-a-directo","errorCode":null,"errorMessage":"sandbox write grant {} is a symlink, not a directory","messagePattern":"sandbox write grant (.+?) is a symlink, not a directory","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/sandbox/src/util/canonical_path.rs","lineNumber":150,"sourceCode":"        {\n            use std::os::unix::fs::OpenOptionsExt as _;\n            // `O_NOFOLLOW` makes a symlink *leaf* open the symlink itself\n            // (harmless with `O_PATH`) rather than its target, so we can detect\n            // and reject it below; intermediate components are still traversed\n            // and caught by the canonical-path comparison.\n            let file = std::fs::OpenOptions::new()\n                .read(true)\n                .custom_flags(libc::O_PATH | libc::O_CLOEXEC | libc::O_NOFOLLOW)\n                .open(&path)?;\n            let fd = OwnedFd::from(file);\n\n            // Reject a symlink leaf outright: a grant must name a real directory,\n            // and `readlink` of an `O_PATH|O_NOFOLLOW` fd on a symlink returns\n            // the symlink's *own* path (equal to `path`), so the comparison\n            // below wouldn't catch it.\n            let stat = nix::sys::stat::fstat(fd.as_raw_fd()).map_err(io::Error::from)?;\n            if stat.st_mode & libc::S_IFMT == libc::S_IFLNK {\n                return Err(io::Error::new(\n                    io::ErrorKind::PermissionDenied,\n                    format!(\n                        \"sandbox write grant {} is a symlink, not a directory\",\n                        path.display()\n                    ),\n                ));\n            }\n\n            // Load-bearing: the pinned inode's real path must still be exactly\n            // the approved canonical path. If any component became a symlink\n            // after approval, the fd resolves elsewhere and this diverges.\n            let current = std::fs::read_link(format!(\"/proc/self/fd/{}\", fd.as_raw_fd()))?;\n            if current != path {\n                return Err(io::Error::new(\n                    io::ErrorKind::PermissionDenied,\n                    format!(\n                        \"sandbox write grant {} was redirected to {}\",\n                        path.display(),","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/sandbox/src/util/canonical_path.rs#L132-L168","documentation":"Validation error in CanonicalPathBuf::from_canonical: the configured sandbox write grant path resolves (via an O_NOFOLLOW open) to a symlink rather than a real directory. Symlink grants are rejected because the sandbox must pin a concrete directory inode, not a path that can be redirected.","triggerScenarios":"Thrown at crates/sandbox/src/util/canonical_path.rs:150 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Grant the real target directory instead of the symlink","Remove the symlink and replace it with an actual directory","If a symlink is intentional, grant its resolved canonical target"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}