keychain: store SecretStr in _d for deep repr masking

This commit is contained in:
Liang Jiaqing committed 2026-05-22 10:21:25 +08:00
1 parent fff2af9c26
commit 1d0dd4e7b2
1 file changed
+14 -7
+14 -7
View File
@@ -9,18 +9,19 @@ _MASK = hashlib.sha256(f"{_user}@ga_keychain".encode()).digest()
def _xor(data: bytes) -> bytes: def _xor(data: bytes) -> bytes:
return bytes(b ^ _MASK[i % len(_MASK)] for i, b in enumerate(data)) 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: class SecretStr:
def __init__(self, name: str, val: str): def __init__(self, name: str, val: str):
self._name, self._val = name, val self._name, self._val = name, val
def use(self) -> str: def use(self) -> str: return self._val
return self._val
def __repr__(self): def __repr__(self):
n = len(self._val) n = len(self._val)
if n <= 4: preview = '***' if n <= 4: preview = '***'
elif n <= 16: preview = f"{self._val[:3]}···{self._val[-3:]}" elif n <= 16: preview = f"{self._val[:3]}···{self._val[-3:]}"
elif n <= 40: preview = f"{self._val[:6]}···{self._val[-6:]} len={n}" elif n <= 40: preview = f"{self._val[:6]}···{self._val[-6:]} len={n}"
else: preview = f"{self._val[:10]}···{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__ __str__ = __repr__
class _Keys: class _Keys:
@@ -28,19 +29,25 @@ class _Keys:
self._d = {} self._d = {}
if _PATH.exists(): if _PATH.exists():
try: 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: except Exception as e:
print(f"[keychain] WARNING: failed to load {_PATH}: {e}") print(f"[keychain] WARNING: failed to load {_PATH}: {e}")
print(f"[keychain] Starting with empty keychain. Old file kept as .bak") print(f"[keychain] Starting with empty keychain. Old file kept as .bak")
_PATH.rename(_PATH.with_suffix('.enc.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): def __getattr__(self, k):
if k.startswith('_'): raise AttributeError(k) if k.startswith('_'): raise AttributeError(k)
if k not in self._d: raise KeyError(f"No secret: {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): def set(self, k, v=None, *, file=None):
if file: v = pathlib.Path(file).read_text().strip() if file: v = pathlib.Path(file).read_text().strip()
self._d[k] = v self._d[k] = SecretStr(k, v)
_PATH.write_bytes(_xor(json.dumps(self._d).encode())) self._save()
def ls(self): return list(self._d.keys()) def ls(self): return list(self._d.keys())
keys = _Keys() keys = _Keys()