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"),
	}, nil

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Use a relative path without .. traversal within the allowed directory
  2. Remove leading / (absolute path) and anchor under the permitted root
  3. Sanitize user-supplied filenames (filepath.Base, strip traversal) before passing as output
  4. 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

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


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/e82924ed397f1740. Report an issue: GitHub.