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.AddressView on GitHub (pinned to 7fd6ef3c07)
Solutions
- Verify the attachment path exists and is inside the allowed root
- Use an absolute, validated path (or one resolved via runtime.ValidatePath/ResolveSavePath) before AddFileAttachment
- Check the wrapped cause in the error to see which path rule rejected it
- 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
- Pass absolute paths inside the allowed root
- Pre-resolve user-supplied paths via runtime.ValidatePath
- Confirm attachment files exist before building the EML
- Avoid relative paths that depend on process working directory
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
- draft has no MIME body
- emlbuilder: header value contains control character: %q
- %s: path must be absolute, got %q
- %s: cannot stat %q: %w
- %s: path %q is a directory, not a file
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/cca783db8945b392.
Report an issue: GitHub.