larksuite/cli · error

attachment %q: %w

Error message

attachment %q: %w

What it means

readFile in the EML builder validates each attachment path with validate.SafeInputPath before opening it. If path validation fails (path outside allowed roots, invalid/unresolvable path, safety rejection), the error is wrapped as `attachment "<path>": <cause>`. This is an intermediate untyped error that the mail command layer later converts into a typed ValidationError, which is why the raw fmt.Errorf is nolint-waived.

Source

Thrown at shortcuts/mail/emlbuilder/builder.go:66

	"math/rand"
	"mime"
	"net/mail"
	"path/filepath"
	"strings"
	"time"

	"github.com/larksuite/cli/extension/fileio"
	"github.com/larksuite/cli/internal/validate"
	"github.com/larksuite/cli/shortcuts/mail/filecheck"
)

// MaxEMLSize is the maximum allowed raw EML size in bytes.
const MaxEMLSize = 25 * 1024 * 1024 // 25 MB

// readFile reads the named file and returns its contents via FileIO.
func readFile(fio fileio.FileIO, path string) ([]byte, error) {
	if _, err := validate.SafeInputPath(path); err != nil {
		return nil, fmt.Errorf("attachment %q: %w", path, err) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
	}
	f, err := fio.Open(path)
	if err != nil {
		return nil, fmt.Errorf("attachment %q: %w", path, err) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
	}
	defer f.Close()
	return io.ReadAll(f)
}

// Builder constructs a Lark-compatible RFC 2822 EML message.
// All setter methods return a copy of the Builder (immutable/fluent style),
// so a base builder can be reused across multiple goroutines safely.
type Builder struct {
	fio                       fileio.FileIO // injected via WithFileIO; must be set before AddFile* calls
	from                      mail.Address
	to                        []mail.Address
	cc                        []mail.Address
	bcc                       []mail.Address

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Verify the attachment path exists and is inside the allowed root
  2. Use an absolute, validated path (or one resolved via runtime.ValidatePath/ResolveSavePath) before AddFileAttachment
  3. Check the wrapped cause in the error to see which path rule rejected it
  4. Copy the file into the allowed workspace tree if it lives outside

Example fix

// before
b.AddFileAttachment(fio, "../secrets/notes.pdf")
// after
b.AddFileAttachment(fio, "/workspace/attachments/notes.pdf") // absolute, allowed root
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range attachmentPaths {
  if _, err := validate.SafeInputPath(p); err != nil {
    return fmt.Errorf("skipping attachment %q: %w", p, err)
  }
}

Try / catch

err := builder.AddFileAttachment(fio, path)
if err != nil {
  var verr *ValidationError
  if errors.As(err, &verr) { return userFacingPathError(path, verr) }
  return err
}

Prevention

When it happens

Trigger: Calling AddFileAttachment, AddFileInline, or AddFileOtherPart with a path that validate.SafeInputPath rejects: relative path with no valid base, traversal outside allowed roots, nonexistent parent resolution, or otherwise unsafe input path.

Common situations: Attaching files from a temp directory that was cleaned up; passing user-supplied relative paths; running under the FileIO-scoped/portable boundary where only validated workspace paths are accepted; typos or stale paths in scripts.

Related errors


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