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

openCredentialFileNoFollow refuses to open a credential file that is not a regular file or is a symbolic link. This is a security guard against symlink-based attacks where an attacker swaps the credential path to point elsewhere (e.g. /proc, /etc/shadow). The file must be a plain regular file at the given path.

Solutions

  1. Replace the symlink with a real regular file containing the credential (e.g. copy the target content to the path)
  2. Fix the deployment/config so the credential path points directly at the regular secret file
  3. If on Linux, note the unix build (credential_file_unix.go) rejects only ELOOP; check whether the file is actually a non-regular file type (FIFO/dir) and correct it

Example fix

// before
ln -s /var/secrets/tenant.key /etc/tenant/credential.key
// after
cp /var/secrets/tenant.key /etc/tenant/credential.key && rm /etc/tenant/credential.key.bak
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Lstat(path)
if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
    return fmt.Errorf("credential path %q must be a regular non-symlink file", path)
}

Type guard

func isRegularNonSymlink(path string) bool {
    info, err := os.Lstat(path)
    return err == nil && info.Mode()&os.ModeSymlink == 0 && 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" {
        // resolve/replace symlink or abort startup with a clear config message
    }
    return err
}

Prevention

When it happens

Trigger: Calling openCredentialFileNoFollow with a path that is a symlink, a directory, a FIFO/socket/device, or otherwise not a regular file (checked via os.Lstat mode).

Common situations: Deployment tools placing credentials behind symlinked paths (e.g. Kubernetes symlinked secret mounts, /etcalternatives-style links); running the tenant service with a config pointing at a symlink created by a provisioning script; mount points or sockets accidentally used as the credential file path.

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/0a1085afb38488c8. Report an issue: GitHub.

Appendix: source

Thrown at core/tenant/config/credential_file_other.go:18

//go:build !linux && !darwin

package config

import (
	"errors"
	"os"
)

// openCredentialFileNoFollow is a portability fallback for platforms without
// O_NOFOLLOW. Supported production images use the Unix implementation above.
func openCredentialFileNoFollow(fileName string) (*os.File, error) {
	pathInfo, err := os.Lstat(fileName)
	if err != nil {
		return nil, errors.New("credential file is unavailable")
	}
	if pathInfo.Mode()&os.ModeSymlink != 0 || !pathInfo.Mode().IsRegular() {
		return nil, errors.New(
			"credential file must be a regular non-symbolic-link file",
		)
	}
	file, err := os.Open(fileName)
	if err != nil {
		return nil, errors.New("credential file is unavailable")
	}
	openedInfo, err := file.Stat()
	if err != nil || !openedInfo.Mode().IsRegular() || !os.SameFile(pathInfo, openedInfo) {
		_ = file.Close()
		return nil, errors.New("credential file changed while being opened")
	}
	return file, nil
}

View on GitHub (pinned to 5e758547a8)