diff --git a/internal/cmdutil/resolve.go b/internal/cmdutil/resolve.go index 3d0d12d4b..a9573fa3e 100644 --- a/internal/cmdutil/resolve.go +++ b/internal/cmdutil/resolve.go @@ -7,9 +7,12 @@ import ( "errors" "fmt" "io" + "path/filepath" "strings" "github.com/larksuite/cli/extension/fileio" + "github.com/larksuite/cli/internal/vfs" + "github.com/larksuite/cli/internal/vfs/localfileio" ) // ResolveInput resolves special input conventions for a raw flag value: @@ -81,6 +84,19 @@ func ReadInputFile(fileIO fileio.FileIO, path string) ([]byte, error) { if fileIO == nil { return nil, fmt.Errorf("file input is not available in this context") } + // For absolute paths, use os.Open directly after safety validation. + // This allows agents and scripts to use /tmp/ and other absolute paths. + if filepath.IsAbs(path) { + safePath, err := localfileio.SafeAbsoluteInputPath(path) + if err != nil { + return nil, wrapInputFileError(path, err) + } + data, err := vfs.ReadFile(safePath) + if err != nil { + return nil, wrapInputFileError(path, err) + } + return data, nil + } f, err := fileIO.Open(path) if err != nil { return nil, wrapInputFileError(path, err) diff --git a/internal/util/proxy.go b/internal/util/proxy.go index d9e251859..bd4907439 100644 --- a/internal/util/proxy.go +++ b/internal/util/proxy.go @@ -58,11 +58,16 @@ func redactProxyURL(raw string) string { // WarnIfProxied prints a one-time warning to w when a proxy environment variable // is detected and proxy is not disabled via LARK_CLI_NO_PROXY. Proxy credentials // are redacted. Safe to call multiple times; only the first call prints. +// Suppressed when LARK_CLI_NO_PROXY_WARNING is set, or in CI environments. func WarnIfProxied(w io.Writer) { proxyWarningOnce.Do(func() { if os.Getenv(EnvNoProxy) != "" { return } + // Suppress proxy warning when explicitly requested or in CI. + if os.Getenv("LARK_CLI_NO_PROXY_WARNING") != "" || os.Getenv("CI") != "" { + return + } key, val := DetectProxyEnv() if key == "" { return diff --git a/internal/validate/path.go b/internal/validate/path.go index 59d21c2c0..b8d71212f 100644 --- a/internal/validate/path.go +++ b/internal/validate/path.go @@ -28,3 +28,10 @@ func SafeEnvDirPath(path, envName string) (string, error) { func SafeLocalFlagPath(flagName, value string) (string, error) { return localfileio.SafeLocalFlagPath(flagName, value) } + +// SafeAbsoluteInputPath validates an absolute path for safety (control +// characters, symlink resolution) without restricting to the working directory. +func SafeAbsoluteInputPath(path string) error { + _, err := localfileio.SafeAbsoluteInputPath(path) + return err +} diff --git a/internal/vfs/localfileio/path.go b/internal/vfs/localfileio/path.go index 7a4b52ab8..c4a8b25e9 100644 --- a/internal/vfs/localfileio/path.go +++ b/internal/vfs/localfileio/path.go @@ -24,10 +24,18 @@ func SafeInputPath(path string) (string, error) { // SafeLocalFlagPath validates a flag value as a local file path. // Empty values and http/https URLs are returned unchanged without validation. +// Absolute paths are accepted and validated for safety (control characters, +// symlink resolution) without restricting to the working directory. func SafeLocalFlagPath(flagName, value string) (string, error) { if value == "" || strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") { return value, nil } + if filepath.IsAbs(value) { + if _, err := safePathAbsolute(value, flagName); err != nil { + return "", err + } + return value, nil + } if _, err := SafeInputPath(value); err != nil { return "", fmt.Errorf("%s: %v", flagName, err) } @@ -92,6 +100,31 @@ func safePath(raw, flagName string) (string, error) { return resolved, nil } +// safePathAbsolute validates an absolute path for safety without restricting +// to the working directory. It rejects control characters and resolves +// symlinks through the nearest existing ancestor. +func safePathAbsolute(raw, flagName string) (string, error) { + if err := charcheck.RejectControlChars(raw, flagName); err != nil { + return "", err + } + + path := filepath.Clean(raw) + + resolved, err := resolveNearestAncestor(path) + if err != nil { + return "", fmt.Errorf("cannot resolve symlinks: %w", err) + } + return resolved, nil +} + +// SafeAbsoluteInputPath validates an absolute path for safety (control +// characters, symlink resolution) without restricting to the working directory. +// This is intended for user-provided flag values where absolute paths are +// explicitly allowed. +func SafeAbsoluteInputPath(path string) (string, error) { + return safePathAbsolute(path, "path") +} + func resolveNearestAncestor(path string) (string, error) { var tail []string cur := path