From 6829f91a062fce6571bf612bf37017578452dd1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 13 Mar 2026 03:27:42 +0800 Subject: [PATCH] ccm,ocm: check credential file writability before token refresh Refuse to refresh tokens when the credential file is not writable, preventing server-side invalidation of the old refresh token that would make the credential permanently unusable after restart. --- service/ccm/credential.go | 8 ++++++++ service/ccm/credential_darwin.go | 7 +++++++ service/ccm/credential_other.go | 11 +++++++++++ service/ccm/credential_state.go | 7 ++++++- service/ocm/credential.go | 8 ++++++++ service/ocm/credential_darwin.go | 11 +++++++++++ service/ocm/credential_other.go | 11 +++++++++++ service/ocm/credential_state.go | 7 ++++++- 8 files changed, 68 insertions(+), 2 deletions(-) diff --git a/service/ccm/credential.go b/service/ccm/credential.go index f14d4d2bc..6b3000861 100644 --- a/service/ccm/credential.go +++ b/service/ccm/credential.go @@ -108,6 +108,14 @@ func readCredentialsFromFile(path string) (*oauthCredentials, error) { return credentialsContainer.ClaudeAIAuth, nil } +func checkCredentialFileWritable(path string) error { + file, err := os.OpenFile(path, os.O_WRONLY, 0) + if err != nil { + return err + } + return file.Close() +} + func writeCredentialsToFile(oauthCredentials *oauthCredentials, path string) error { data, err := json.MarshalIndent(map[string]any{ "claudeAiOauth": oauthCredentials, diff --git a/service/ccm/credential_darwin.go b/service/ccm/credential_darwin.go index 24047b858..aef10c874 100644 --- a/service/ccm/credential_darwin.go +++ b/service/ccm/credential_darwin.go @@ -69,6 +69,13 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(defaultPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + return nil + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { if customPath != "" { return writeCredentialsToFile(oauthCredentials, customPath) diff --git a/service/ccm/credential_other.go b/service/ccm/credential_other.go index 11888b508..02c52e71e 100644 --- a/service/ccm/credential_other.go +++ b/service/ccm/credential_other.go @@ -13,6 +13,17 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(customPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { if customPath == "" { var err error diff --git a/service/ccm/credential_state.go b/service/ccm/credential_state.go index 937129597..ff64b24bc 100644 --- a/service/ccm/credential_state.go +++ b/service/ccm/credential_state.go @@ -225,6 +225,11 @@ func (c *defaultCredential) getAccessToken() (string, error) { return c.credentials.AccessToken, nil } + err = platformCanWriteCredentials(c.credentialPath) + if err != nil { + return "", E.Cause(err, "credential file not writable, refusing refresh to avoid invalidation") + } + baseCredentials := cloneCredentials(c.credentials) newCredentials, err := refreshToken(c.httpClient, c.credentials) if err != nil { @@ -258,7 +263,7 @@ func (c *defaultCredential) getAccessToken() (string, error) { err = platformWriteCredentials(newCredentials, c.credentialPath) if err != nil { - c.logger.Warn("persist refreshed token for ", c.tag, ": ", err) + c.logger.Error("persist refreshed token for ", c.tag, ": ", err) } return newCredentials.AccessToken, nil diff --git a/service/ocm/credential.go b/service/ocm/credential.go index f16beb916..c143f868a 100644 --- a/service/ocm/credential.go +++ b/service/ocm/credential.go @@ -55,6 +55,14 @@ func readCredentialsFromFile(path string) (*oauthCredentials, error) { return &credentials, nil } +func checkCredentialFileWritable(path string) error { + file, err := os.OpenFile(path, os.O_WRONLY, 0) + if err != nil { + return err + } + return file.Close() +} + func writeCredentialsToFile(credentials *oauthCredentials, path string) error { data, err := json.MarshalIndent(credentials, "", " ") if err != nil { diff --git a/service/ocm/credential_darwin.go b/service/ocm/credential_darwin.go index f3da2a63e..37e7c1c7a 100644 --- a/service/ocm/credential_darwin.go +++ b/service/ocm/credential_darwin.go @@ -13,6 +13,17 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(customPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { if customPath == "" { var err error diff --git a/service/ocm/credential_other.go b/service/ocm/credential_other.go index 22dfd0337..9da2a569d 100644 --- a/service/ocm/credential_other.go +++ b/service/ocm/credential_other.go @@ -13,6 +13,17 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(customPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { if customPath == "" { var err error diff --git a/service/ocm/credential_state.go b/service/ocm/credential_state.go index e6c0642e4..547926b87 100644 --- a/service/ocm/credential_state.go +++ b/service/ocm/credential_state.go @@ -234,6 +234,11 @@ func (c *defaultCredential) getAccessToken() (string, error) { return c.credentials.getAccessToken(), nil } + err = platformCanWriteCredentials(c.credentialPath) + if err != nil { + return "", E.Cause(err, "credential file not writable, refusing refresh to avoid invalidation") + } + baseCredentials := cloneCredentials(c.credentials) newCredentials, err := refreshToken(c.httpClient, c.credentials) if err != nil { @@ -265,7 +270,7 @@ func (c *defaultCredential) getAccessToken() (string, error) { err = platformWriteCredentials(newCredentials, c.credentialPath) if err != nil { - c.logger.Warn("persist refreshed token for ", c.tag, ": ", err) + c.logger.Error("persist refreshed token for ", c.tag, ": ", err) } return newCredentials.getAccessToken(), nil