{"record":{"id":"4234ef704a01ebec","repo":"iflytek/astron-agent","slug":"credential-file-changed-while-being-opened","errorCode":null,"errorMessage":"credential file changed while being opened","messagePattern":"credential file changed while being opened","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/tenant/config/credential_file_other.go","lineNumber":29,"sourceCode":"// O_NOFOLLOW. Supported production images use the Unix implementation above.\nfunc openCredentialFileNoFollow(fileName string) (*os.File, error) {\n\tpathInfo, err := os.Lstat(fileName)\n\tif err != nil {\n\t\treturn nil, errors.New(\"credential file is unavailable\")\n\t}\n\tif pathInfo.Mode()&os.ModeSymlink != 0 || !pathInfo.Mode().IsRegular() {\n\t\treturn nil, errors.New(\n\t\t\t\"credential file must be a regular non-symbolic-link file\",\n\t\t)\n\t}\n\tfile, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn nil, errors.New(\"credential file is unavailable\")\n\t}\n\topenedInfo, err := file.Stat()\n\tif err != nil || !openedInfo.Mode().IsRegular() || !os.SameFile(pathInfo, openedInfo) {\n\t\t_ = file.Close()\n\t\treturn nil, errors.New(\"credential file changed while being opened\")\n\t}\n\treturn file, nil\n}\n","sourceCodeStart":11,"sourceCodeEnd":33,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/tenant/config/credential_file_other.go#L11-L33","documentation":"After opening, openCredentialFileNoFollow re-stats the open file and compares it (os.SameFile) with the pre-open Lstat result. If the stat fails, the file is no longer a regular file, or the inode changed, it concludes the credential file was replaced or modified during the open window and refuses to return the handle. This closes the TOCTOU race where a symlink/regular-file swap happens between check and open.","triggerScenarios":"File replaced (rename/unlink+create) between Lstat and Open; Stat on the open fd fails; the opened fd points at a non-regular file; hard link swapped to a different inode mid-open.","commonSituations":"Secret rotation tools (vault-agent, consul-template, k8s secret remount) atomically replacing the credential file exactly while the service is starting; concurrent deploys writing the credential file; automated config sync jobs racing service startup.","solutions":["Retry the startup once the credential rotation completes; make rotation tools pause or signal the service after writing","Use atomic write conventions that finish before the service reads (write temp + rename before service start), or restart the service after rotation","Serialize credential deployment with service startup (dependency ordering in systemd/compose)","Check for competing processes (two rotation agents) writing the same path"],"exampleFix":"// before\n* * * * * /usr/bin/fetch-secret > /etc/tenant/credential.key   # overwrites in place, races readers\n// after\n* * * * * /usr/bin/fetch-secret > /etc/tenant/credential.key.tmp && mv /etc/tenant/credential.key.tmp /etc/tenant/credential.key && systemctl try-restart tenant","handlingStrategy":"retry","validationCode":"// No reliable pre-check can prevent an open-window swap; instead, after opening,\n// confirm stability:\ninfo1, _ := os.Stat(path)\ninfo2, _ := os.Open(path) // then stat fd and os.SameFile(info1, fdInfo)","typeGuard":null,"tryCatchPattern":"var f *os.File\nerr := retry.Do(func() error {\n    var err error\n    f, err = openCredentialFileNoFollow(path)\n    return err\n}, retry.Attempts(3), retry.Delay(100*time.Millisecond))\nif err != nil {\n    return fmt.Errorf(\"credential file kept changing during open (rotation race?): %w\", err)\n}","preventionTips":["Schedule secret rotation outside service startup windows, or restart the service after rotation","Use write-temp-then-rename atomic updates in rotation tooling","Serialize deployment steps: finish credential writes before starting the service","Add retry-with-backoff around credential opening at startup"],"tags":["go","filesystem","race-condition","credentials"],"backgroundTag":"invalid-state-transition","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"}