{"record":{"id":"e82924ed397f1740","repo":"larksuite/cli","slug":"unsafe-output-path-w","errorCode":null,"errorMessage":"unsafe output path: %w","messagePattern":"unsafe output path: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/response.go","lineNumber":218,"sourceCode":"\t}\n\treturn result, nil\n}\n\n// ── File saving ──\n\n// SaveResponse writes an API response body to the given outputPath and returns metadata.\n// It delegates to FileIO.Save for path validation and atomic write; fio must not be nil.\nfunc SaveResponse(fio fileio.FileIO, resp *larkcore.ApiResp, outputPath string) (map[string]interface{}, error) {\n\tresult, err := fio.Save(outputPath, fileio.SaveOptions{\n\t\tContentType:   resp.Header.Get(\"Content-Type\"),\n\t\tContentLength: int64(len(resp.RawBody)),\n\t}, bytes.NewReader(resp.RawBody))\n\tif err != nil {\n\t\tvar me *fileio.MkdirError\n\t\tvar we *fileio.WriteError\n\t\tswitch {\n\t\tcase errors.Is(err, fileio.ErrPathValidation):\n\t\t\treturn nil, fmt.Errorf(\"unsafe output path: %w\", err)\n\t\tcase errors.As(err, &me):\n\t\t\treturn nil, fmt.Errorf(\"create directory: %w\", err)\n\t\tcase errors.As(err, &we):\n\t\t\treturn nil, fmt.Errorf(\"cannot write file: %w\", err)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"cannot write file: %w\", err)\n\t\t}\n\t}\n\n\tresolvedPath, err := fio.ResolvePath(outputPath)\n\tif err != nil || resolvedPath == \"\" {\n\t\tresolvedPath = outputPath\n\t}\n\treturn map[string]interface{}{\n\t\t\"saved_path\":   resolvedPath,\n\t\t\"size_bytes\":   result.Size(),\n\t\t\"content_type\": resp.Header.Get(\"Content-Type\"),\n\t}, nil","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/client/response.go#L200-L236","documentation":"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.","triggerScenarios":"Calling a command with an output path containing \"..\" segments, an absolute path when validation requires relative paths, or any path rejected by fileio.ErrPathValidation.","commonSituations":"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.","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"],"exampleFix":"// before\n--output /etc/out/../../tmp/result.json\n// after\n--output results/result.json","handlingStrategy":"validation","validationCode":"func safeOutputPath(p string) (string, error) {\n    if filepath.IsAbs(p) {\n        return \"\", fmt.Errorf(\"absolute output path not allowed: %s\", p)\n    }\n    clean := filepath.Clean(p)\n    if strings.HasPrefix(clean, \"..\") {\n        return \"\", fmt.Errorf(\"path traversal not allowed: %s\", p)\n    }\n    return clean, nil\n}","typeGuard":null,"tryCatchPattern":"if err := SaveResponse(resp, outPath); err != nil {\n    if strings.Contains(err.Error(), \"unsafe output path\") {\n        return fmt.Errorf(\"refusing to write outside allowed dir: %w\", err)\n    }\n    return err\n}","preventionTips":["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"],"tags":["filesystem","security","path-validation"],"backgroundTag":"unsafe-output-path","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}