Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions internal/cmdutil/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
f, err := fileIO.Open(path)
if err != nil {
return nil, wrapInputFileError(path, err)
Expand Down
5 changes: 5 additions & 0 deletions internal/util/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions internal/validate/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
33 changes: 33 additions & 0 deletions internal/vfs/localfileio/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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")
}
Comment thread
EvanYao826 marked this conversation as resolved.

func resolveNearestAncestor(path string) (string, error) {
var tail []string
cur := path
Expand Down
Loading