larksuite/cli · error
unsafe output path: %w
Error message
unsafe output path: %w
What it means
SaveResponse wraps fileio.ErrPathValidation as "unsafe output path" when the --output/-o target fails FileIO path validation — e.g. path traversal or absolute paths outside allowed roots. This is a deliberate safety gate, not an I/O failure.
Source
Thrown at internal/client/response.go:218
}
return result, nil
}
// ── File saving ──
// SaveResponse writes an API response body to the given outputPath and returns metadata.
// It delegates to FileIO.Save for path validation and atomic write; fio must not be nil.
func SaveResponse(fio fileio.FileIO, resp *larkcore.ApiResp, outputPath string) (map[string]interface{}, error) {
result, err := fio.Save(outputPath, fileio.SaveOptions{
ContentType: resp.Header.Get("Content-Type"),
ContentLength: int64(len(resp.RawBody)),
}, bytes.NewReader(resp.RawBody))
if err != nil {
var me *fileio.MkdirError
var we *fileio.WriteError
switch {
case errors.Is(err, fileio.ErrPathValidation):
return nil, fmt.Errorf("unsafe output path: %w", err)
case errors.As(err, &me):
return nil, fmt.Errorf("create directory: %w", err)
case errors.As(err, &we):
return nil, fmt.Errorf("cannot write file: %w", err)
default:
return nil, fmt.Errorf("cannot write file: %w", err)
}
}
resolvedPath, err := fio.ResolvePath(outputPath)
if err != nil || resolvedPath == "" {
resolvedPath = outputPath
}
return map[string]interface{}{
"saved_path": resolvedPath,
"size_bytes": result.Size(),
"content_type": resp.Header.Get("Content-Type"),
}, nilView on GitHub (pinned to 7fd6ef3c07)
Solutions
- Use a relative path without .. traversal within the allowed directory
- Remove leading / (absolute path) and anchor under the permitted root
- Sanitize user-supplied filenames (filepath.Base, strip traversal) before passing as output
- If host-local file access is intended, follow the FileIO/ValidatePath contract rather than bypassing
Example fix
// before --output /etc/out/../../tmp/result.json // after --output results/result.json
Defensive patterns
Strategy: validation
Validate before calling
func safeOutputPath(p string) (string, error) {
if filepath.IsAbs(p) {
return "", fmt.Errorf("absolute output path not allowed: %s", p)
}
clean := filepath.Clean(p)
if strings.HasPrefix(clean, "..") {
return "", fmt.Errorf("path traversal not allowed: %s", p)
}
return clean, nil
} Try / catch
if err := SaveResponse(resp, outPath); err != nil {
if strings.Contains(err.Error(), "unsafe output path") {
return fmt.Errorf("refusing to write outside allowed dir: %w", err)
}
return err
} Prevention
- Derive output filenames from filepath.Base(userInput)
- Never join user strings directly into output paths without cleaning
- Run commands with CWD inside the permitted output root
- Add a unit test asserting traversal inputs are rejected
When it happens
Trigger: Calling a command with an output path containing ".." segments, an absolute path when validation requires relative paths, or any path rejected by fileio.ErrPathValidation.
Common situations: Scripting with user-derived filenames that include ../; migrating commands that previously accepted absolute paths; running in containerized/FileIO-scoped environments where host CWD assumptions break.
Related errors
- %s: path must be absolute, got %q
- %s: cannot stat %q: %w
- %s: path %q is a directory, not a file
- %s: path %q is a symlink (not allowed)
- %s %q has multiple hard links, so writing it would also rewr
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/e82924ed397f1740.
Report an issue: GitHub.