{"record":{"id":"02bef6783211570c","repo":"iflytek/astron-agent","slug":"credential-file-must-be-a-regular-non-symbolic-link-file","errorCode":null,"errorMessage":"credential file must be a regular non-symbolic-link file","messagePattern":"credential file must be a regular non-symbolic-link file","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/tenant/config/bootstrap_credentials.go","lineNumber":120,"sourceCode":"\t}\n\treturn value, nil\n}\n\nfunc readCredentialFile(fileName string) (string, error) {\n\tfile, err := openCredentialFileNoFollow(fileName)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\topenedInfo, err := file.Stat()\n\tif err != nil {\n\t\treturn \"\", errors.New(\"credential file cannot be inspected\")\n\t}\n\tif !openedInfo.Mode().IsRegular() {\n\t\treturn \"\", errors.New(\"credential file must be a regular non-symbolic-link file\")\n\t}\n\tif openedInfo.Size() > maxCredentialFileBytes {\n\t\treturn \"\", errors.New(\"credential file is too large\")\n\t}\n\n\tdata, err := io.ReadAll(io.LimitReader(file, maxCredentialFileBytes+1))\n\tif err != nil {\n\t\treturn \"\", errors.New(\"credential file cannot be read\")\n\t}\n\tif len(data) > maxCredentialFileBytes {\n\t\treturn \"\", errors.New(\"credential file is too large\")\n\t}\n\treturn strings.TrimSpace(string(data)), nil\n}\n\nfunc validateCredential(name, value string) error {\n\tlength := utf8.RuneCountInString(value)\n\tif !utf8.ValidString(value) || length < tenantCredentialMinLength || length > tenantCredentialMaxLength {","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/tenant/config/bootstrap_credentials.go#L102-L138","documentation":"readCredentialFile requires the credential file to be a regular file and not a symbolic link; Stat()'s mode is checked and non-regular files (symlink, directory, fifo, device) are rejected. This prevents symlink-based attacks and accidental misconfiguration of the credential path.","triggerScenarios":"credentialFromEnvironmentOrFile resolves a *_FILE env var to a path; the opened file's Stat().Mode() is not IsRegular() — e.g. the path is a symlink (including an attacked symlink to /etc/shadow) or a named pipe.","commonSituations":"Operator symlinked a credential file for convenience instead of mounting it directly, a tmpfs/fifo was used, or a container secret mount created an intermediate symlink.","solutions":["Replace the symlink with a real regular file (copy the content, do not link)","Mount the credential directly (e.g. Kubernetes secret volume files are fine — use the resolved path under ..data only via the mount)","Verify with `stat -c %F <path>` that it reports 'regular file'"],"exampleFix":"# before\nln -s /etc/tenant/secret.pem /creds/TENANT_SECRET\n# after\ncp /etc/tenant/secret.pem /creds/TENANT_SECRET && chmod 600 /creds/TENANT_SECRET","handlingStrategy":"validation","validationCode":"info, err := os.Lstat(path)\nif err != nil || info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {\n    return fmt.Errorf(\"%s must be a regular file, not a symlink\", path)\n}","typeGuard":null,"tryCatchPattern":"if err := config.ValidateCredentialPaths(map[string]string{\"TENANT_KEY_FILE\": keyPath, \"TENANT_SECRET_FILE\": secretPath}); err != nil {\n    logger.Fatal(\"fix credential mounts: only regular files allowed\")\n}","preventionTips":["Never symlink credential files; copy or mount them directly","Check mounts with `stat -c '%F %N'` during image/manifest review","Keep credential directories (e.g. /creds) free of pipes and devices"],"tags":["filesystem","security","symlink","credentials"],"backgroundTag":"invalid-argument-value","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}