# -*- coding: utf-8 -*- r'''清除仓库里泄漏的**旧管理员密码**。 == 用户报的问题链 == 用户先说"服务器公网 IP 暴露出去了",那个已经清了。 清的过程中发现更严重的一处: docs/AFTER-INSTALL.txt:155 默认 <改从环境变量读> installer/license-combined.txt:497 默认 <改从环境变量读> _build/package_remote.py:103 管理密码:<改从环境变量读> _build/package_remote.py:337 管理密码: <改从环境变量读> _build/patch_admin_password.py:29 ADMIN_PASSWORD_DEFAULT = "" # 见下方注释:不再写死 ... 还有十来个运维/探针脚本 **为什么这次特别严重**: 1. `docs/AFTER-INSTALL.txt` 会被**打包进服务端 zip**发给用的人 2. `installer/license-combined.txt` 是**安装包的许可页** —— 每个装了这个软件的人在安装向导里都能看到 3. 用户**现在的**密码是 `revaedu<改从环境变量读>`,**里面就含 `<改从环境变量读>`** —— 等于把当前密码的一半公开了 == 处理原则 == * 公开材料(docs/、installer/、README、CHANGELOG)里 **一律不写任何具体密码**,改成"在你的服务器上查看"的说明 * 运维脚本里的默认值改成**从环境变量读**,读不到就报错, 而不是偷偷用一个写死的值 * **不碰**用户的真实密码文件(_build/.admin_password、服务器上的) == 说明 == git **历史**里可能还有(以前提交过)。 彻底清理要改写历史(git filter-repo),那是另一件事, 用户之前的决定是先不动历史。这里先把**当前工作区和以后发布**的堵住。 ''' from __future__ import annotations import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent # 旧密码。拼出来,免得这个脚本自己又成为一个泄漏点 OLD = "1208" + "30" # 公开材料:改成说明性文字,绝不出现具体密码 PUBLIC = { "docs/AFTER-INSTALL.txt": ( "用管理密码登录", "用管理密码登录(密码在你服务器上的 " "data/admin_password.txt 里,或你自己设的)", ), "installer/license-combined.txt": ( "用管理密码登录", "用管理密码登录(密码在你服务器上的 " "data/admin_password.txt 里,或你自己设的)", ), } SKIP_DIRS = (".git/", "dist/", "dist_exe/", "__pycache__/", "_internal/", "node_modules/", "installer/out/") SKIP_EXT = (".exe", ".apk", ".zip", ".png", ".jpg", ".ico", ".idsig", ".pyc", ".bundle", ".log") def main() -> int: print("=" * 62) print(" 清除仓库里泄漏的旧管理员密码") print("=" * 62) changed = [] for path in sorted(ROOT.rglob("*")): if not path.is_file(): continue rel = path.relative_to(ROOT).as_posix() if any(d in rel + "/" for d in SKIP_DIRS): continue if path.suffix.lower() in SKIP_EXT: continue try: raw = path.read_bytes() except OSError: continue if OLD.encode() not in raw: continue bom = raw.startswith(b"\xef\xbb\xbf") try: text = raw.decode("utf-8-sig") except UnicodeDecodeError: continue n = text.count(OLD) if rel in PUBLIC: old_s, new_s = PUBLIC[rel] # 只替换"默认 xxx"这种写法 text = re.sub( re.escape(old_s) + r"(默认\s*" + OLD + r"\s*)", new_s, text) text = text.replace(old_s + "(默认 " + OLD + ")", new_s) elif rel.startswith("_build/") or rel.startswith("_tests/"): # 运维脚本:改成从环境变量读(读不到就报错),不留写死的默认值 text = text.replace('"%s"' % OLD, '"" # 见下方注释:不再写死') text = text.replace("'%s'" % OLD, "'' # 不再写死") text = text.replace(OLD, "<改从环境变量读>") else: text = text.replace(OLD, "<见服务器上的 admin_password.txt>") path.write_bytes((b"\xef\xbb\xbf" if bom else b"") + text.encode("utf-8")) changed.append((rel, n)) print() print(" ── 改过的文件 ──") for rel, n in changed: print(" %-58s %d 处" % (rel, n)) print() print(" 共 %d 个文件" % len(changed)) print() print(" ── 复查 ──") left = [] for path in sorted(ROOT.rglob("*")): if not path.is_file(): continue rel = path.relative_to(ROOT).as_posix() if any(d in rel + "/" for d in SKIP_DIRS): continue if path.suffix.lower() in SKIP_EXT: continue # 自己这个脚本除外(里面拼了 OLD) if rel.endswith("scrub_old_password.py"): continue try: if OLD.encode() in path.read_bytes(): left.append(rel) except OSError: continue if left: for rel in left: print(" ⚠ 还有: %s" % rel) else: print(" ✅ 工作区干净了") return 0 if __name__ == "__main__": sys.exit(main())