iflytek/astron-agent · error

credential file must be a regular non-symbolic-link file

Error message

credential file must be a regular non-symbolic-link file

What it means

The unix build of openCredentialFileNoFollow opens the credential file with O_NOFOLLOW; when the kernel returns ELOOP the path is a symbolic link and the function rejects it. Like the portable variant, this prevents symlink attacks on credential paths. A real regular file must be present at the path.

Solutions

  1. Pass the fully resolved real path to the regular file (resolve the symlink outside the library, e.g. filepath.EvalSymlinks before calling, then verify the result is regular)
  2. Configure the credential path to the final regular file rather than a symlinked indirection (e.g. mount secrets with subPath in Kubernetes)
  3. Replace the symlink with a real copy of the secret file

Example fix

// before
cfg.CredentialFile = "/run/secrets/tenant.key"        // symlink created by secret store
// after
real, err := filepath.EvalSymlinks("/run/secrets/tenant.key")
cfg.CredentialFile = real                              // resolved regular file
Defensive patterns

Strategy: validation

Validate before calling

real, err := filepath.EvalSymlinks(path)
if err != nil {
    return err
}
info, err := os.Lstat(real)
if err != nil || !info.Mode().IsRegular() {
    return fmt.Errorf("credential path %q must resolve to a regular file", path)
}

Type guard

func isRealRegularFile(path string) bool {
    real, err := filepath.EvalSymlinks(path)
    if err != nil {
        return false
    }
    info, err := os.Lstat(real)
    return err == nil && info.Mode().IsRegular()
}

Try / catch

f, err := openCredentialFileNoFollow(path)
if err != nil {
    if err.Error() == "credential file must be a regular non-symbolic-link file" {
        resolved, rerr := filepath.EvalSymlinks(path)
        if rerr == nil {
            f, err = openCredentialFileNoFollow(resolved)
        }
    }
    if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: Calling openCredentialFileNoFollow on Linux/Unix with fileName being a symlink (unix.Open returns ELOOP due to O_NOFOLLOW).

Common situations: Kubernetes-mounted secrets are symlinked files inside ..data directories — passing the symlinked leaf path directly triggers this; provisioning scripts creating symlinks to secrets; Docker secrets mounted via symlinks (e.g. /run/secrets/... indirection).

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/17023bbfde0dcf26. Report an issue: GitHub.

Appendix: source

Thrown at core/tenant/config/credential_file_unix.go:24

	"errors"
	"os"

	"golang.org/x/sys/unix"
)

// openCredentialFileNoFollow resolves and opens the credential in one kernel
// operation. O_NOFOLLOW prevents a path swap to a symbolic link between a
// separate path inspection and open; O_NONBLOCK prevents a hostile FIFO from
// blocking startup before the descriptor type is checked with fstat.
func openCredentialFileNoFollow(fileName string) (*os.File, error) {
	fd, err := unix.Open(
		fileName,
		unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW|unix.O_NONBLOCK,
		0,
	)
	if err != nil {
		if errors.Is(err, unix.ELOOP) {
			return nil, errors.New(
				"credential file must be a regular non-symbolic-link file",
			)
		}
		return nil, errors.New("credential file is unavailable")
	}
	return os.NewFile(uintptr(fd), fileName), nil
}

View on GitHub (pinned to 5e758547a8)