@@ -99,9 +99,10 @@ def _verified_binary(self) -> Path:
9999 raise ValueError ("macOS Keychain control is unsafe" )
100100 return self .binary
101101
102- def _execution_context (self ) -> tuple [Path , str , Path , dict [str , str ]]:
102+ def _execution_context (
103+ self , account : str
104+ ) -> tuple [Path , str , Path , dict [str , str ]]:
103105 binary = self ._verified_binary ()
104- account = _keychain_account ()
105106 home = current_user_home ()
106107 environment = {
107108 "HOME" : str (home ),
@@ -115,6 +116,15 @@ def _execution_context(self) -> tuple[Path, str, Path, dict[str, str]]:
115116 }
116117 return binary , account , home , environment
117118
119+ @staticmethod
120+ def _validate_scope (service : str , account : str , expected_account : str ) -> None :
121+ if (
122+ CLAUDE_KEYCHAIN_SERVICE .fullmatch (service ) is None
123+ or not account
124+ or account != expected_account
125+ ):
126+ raise ValueError ("refusing an unscoped Claude Keychain operation" )
127+
118128 @staticmethod
119129 def _prefix (
120130 binary : Path ,
@@ -123,16 +133,15 @@ def _prefix(
123133 account : str ,
124134 expected_account : str ,
125135 ) -> list [str ]:
126- if (
127- CLAUDE_KEYCHAIN_SERVICE .fullmatch (service ) is None
128- or not account
129- or account != expected_account
130- ):
131- raise ValueError ("refusing an unscoped Claude Keychain operation" )
136+ SecurityKeychain ._validate_scope (service , account , expected_account )
132137 return [str (binary ), operation , "-s" , service , "-a" , account ]
133138
134139 def exists (self , service : str , account : str ) -> bool :
135- binary , expected_account , home , environment = self ._execution_context ()
140+ expected_account = _keychain_account ()
141+ self ._validate_scope (service , account , expected_account )
142+ binary , expected_account , home , environment = self ._execution_context (
143+ expected_account
144+ )
136145 result = subprocess .run (
137146 self ._prefix (
138147 binary ,
@@ -155,7 +164,12 @@ def exists(self, service: str, account: str) -> bool:
155164 def copy (self , source : str , destination : str , account : str ) -> None :
156165 # `security -w` writes the secret only to the pipe. The destination
157166 # command receives it through stdin because -w is its final option.
158- binary , expected_account , home , environment = self ._execution_context ()
167+ expected_account = _keychain_account ()
168+ self ._validate_scope (source , account , expected_account )
169+ self ._validate_scope (destination , account , expected_account )
170+ binary , expected_account , home , environment = self ._execution_context (
171+ expected_account
172+ )
159173 reader = subprocess .Popen (
160174 [
161175 * self ._prefix (
@@ -205,7 +219,11 @@ def copy(self, source: str, destination: str, account: str) -> None:
205219 raise ValueError ("scoped Claude Keychain copy failed" )
206220
207221 def delete (self , service : str , account : str , * , missing_ok : bool = False ) -> None :
208- binary , expected_account , home , environment = self ._execution_context ()
222+ expected_account = _keychain_account ()
223+ self ._validate_scope (service , account , expected_account )
224+ binary , expected_account , home , environment = self ._execution_context (
225+ expected_account
226+ )
209227 result = subprocess .run (
210228 self ._prefix (
211229 binary ,
0 commit comments