From 1d0dd4e7b2c565a7c2ae6c0139b0a7bd2579cd3f Mon Sep 17 00:00:00 2001 From: Liang Jiaqing Date: Fri, 22 May 2026 10:21:25 +0800 Subject: [PATCH] keychain: store SecretStr in _d for deep repr masking --- memory/keychain.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/memory/keychain.py b/memory/keychain.py index eb5acc09..e396aa51 100644 --- a/memory/keychain.py +++ b/memory/keychain.py @@ -9,18 +9,19 @@ _MASK = hashlib.sha256(f"{_user}@ga_keychain".encode()).digest() def _xor(data: bytes) -> bytes: return bytes(b ^ _MASK[i % len(_MASK)] for i, b in enumerate(data)) +print('# SecretStr.use() to get raw, do not print raw value! | keys.ls() to list all keys') + class SecretStr: def __init__(self, name: str, val: str): self._name, self._val = name, val - def use(self) -> str: - return self._val + def use(self) -> str: return self._val def __repr__(self): n = len(self._val) if n <= 4: preview = '***' elif n <= 16: preview = f"{self._val[:3]}···{self._val[-3:]}" elif n <= 40: preview = f"{self._val[:6]}···{self._val[-6:]} len={n}" else: preview = f"{self._val[:10]}···{self._val[-6:]} len={n}" - return f"SecretStr({self._name}={preview}) # .use() to get raw, do not print raw value" + return f"SecretStr({self._name}={preview})" __str__ = __repr__ class _Keys: @@ -28,19 +29,25 @@ class _Keys: self._d = {} if _PATH.exists(): try: - self._d = json.loads(_xor(_PATH.read_bytes())) + raw = json.loads(_xor(_PATH.read_bytes())) + self._d = {k: SecretStr(k, v) for k, v in raw.items()} except Exception as e: print(f"[keychain] WARNING: failed to load {_PATH}: {e}") print(f"[keychain] Starting with empty keychain. Old file kept as .bak") _PATH.rename(_PATH.with_suffix('.enc.bak')) + def _save(self): + raw = {k: v.use() for k, v in self._d.items()} + _PATH.write_bytes(_xor(json.dumps(raw).encode())) def __getattr__(self, k): if k.startswith('_'): raise AttributeError(k) if k not in self._d: raise KeyError(f"No secret: {k}") - return SecretStr(k, self._d[k]) + return self._d[k] + def __repr__(self): + return f"Keychain({len(self._d)} secrets: {', '.join(self._d.keys())})" def set(self, k, v=None, *, file=None): if file: v = pathlib.Path(file).read_text().strip() - self._d[k] = v - _PATH.write_bytes(_xor(json.dumps(self._d).encode())) + self._d[k] = SecretStr(k, v) + self._save() def ls(self): return list(self._d.keys()) keys = _Keys()