first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""发布目录检查:只报告文件与问题类型,不输出密钥或个人标记原文。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ALLOWED_DLLS = {'native/build_text/text_bridge.dll', 'native/build/selftest_bridge.dll'}
|
||||
SECRET_PATTERNS = {
|
||||
'possible_api_key': re.compile(r'(?<![A-Za-z0-9])sk-[A-Za-z0-9_-]{16,}'),
|
||||
'possible_github_token': re.compile(r'(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{30,})'),
|
||||
'private_key': re.compile(r'-----BEGIN [A-Z ]*PRIVATE KEY-----'),
|
||||
'absolute_user_path': re.compile(r'[A-Za-z]:[\\/]+Users[\\/]+[^\\/\s\"\x00]+', re.IGNORECASE),
|
||||
'possible_phone_number': re.compile(r'(?<![A-Za-z0-9_])1[3-9][0-9]{9}(?![A-Za-z0-9_])'),
|
||||
}
|
||||
|
||||
|
||||
def inspect(root, private_markers=()):
|
||||
findings, count, total = [], 0, 0
|
||||
forbidden_dirs = set()
|
||||
for path in sorted(root.rglob('*')):
|
||||
relative = path.relative_to(root)
|
||||
if '.git' in relative.parts:
|
||||
continue
|
||||
if path.is_dir():
|
||||
if path.name in ('.runtime','logs','__pycache__','.venv','venv','.build') or path.name.startswith(('snapshot_','ghidra_')):
|
||||
forbidden_dirs.add(relative.as_posix())
|
||||
continue
|
||||
if not path.is_file():
|
||||
continue
|
||||
count += 1
|
||||
raw = path.read_bytes()
|
||||
total += len(raw)
|
||||
label = relative.as_posix()
|
||||
if path.suffix.lower() in ('.pyc','.obj','.pdb','.lib','.exp','.exe','.dmp','.db','.sqlite','.sqlite3','.bak','.zip'):
|
||||
findings.append({'file':label,'kind':'unnecessary_or_private_artifact'})
|
||||
if path.suffix.lower() == '.dll' and label not in ALLOWED_DLLS:
|
||||
findings.append({'file':label,'kind':'unexpected_binary'})
|
||||
for kind, pattern in SECRET_PATTERNS.items():
|
||||
# DLLs and other native files can also carry UTF-16 paths or credentials.
|
||||
variants = (raw.decode('utf-8',errors='ignore'),raw.decode('utf-16le',errors='ignore'),
|
||||
raw[1:].decode('utf-16le',errors='ignore'))
|
||||
if any(pattern.search(text) for text in variants):
|
||||
findings.append({'file':label,'kind':kind})
|
||||
if any(marker and (marker.encode('utf-8') in raw or marker.encode('utf-16le') in raw)
|
||||
for marker in private_markers):
|
||||
findings.append({'file':label,'kind':'known_private_marker'})
|
||||
findings.extend({'file':name,'kind':'runtime_or_legacy_directory'} for name in sorted(forbidden_dirs))
|
||||
for name in ('bot_config','members_config','llm_config'):
|
||||
template = root / (name + '.example.json')
|
||||
if not template.is_file():
|
||||
findings.append({'file':template.name,'kind':'missing_config_template'})
|
||||
for filename in ('bot_config.json','bot_config.example.json'):
|
||||
path = root / filename
|
||||
if path.exists():
|
||||
data=json.loads(path.read_text(encoding='utf-8'))
|
||||
if data.get('wechat_account') not in (None,'wxid_example_robot') or data.get('wechat_pid') is not None or data.get('chat_allowlist'):
|
||||
findings.append({'file':filename,'kind':'nonempty_account_or_chat_binding'})
|
||||
if data.get('plugins',{}).get('idiom_chain',{}).get('member_names'):
|
||||
findings.append({'file':filename,'kind':'nonempty_member_names'})
|
||||
for filename in ('members_config.json','members_config.example.json'):
|
||||
path=root/filename
|
||||
if path.exists() and json.loads(path.read_text(encoding='utf-8')).get('groups'):
|
||||
findings.append({'file':filename,'kind':'nonempty_group_bindings'})
|
||||
for filename in ('llm_config.json','llm_config.example.json'):
|
||||
path=root/filename
|
||||
if path.exists() and json.loads(path.read_text(encoding='utf-8')).get('api_key'):
|
||||
findings.append({'file':filename,'kind':'nonempty_api_key'})
|
||||
return {'passed':not findings,'files_scanned':count,'bytes_scanned':total,'findings':findings}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--private-markers-file',type=Path,help='可选:发布目录外的本地标记清单,不会输出其内容')
|
||||
args=parser.parse_args()
|
||||
markers=[]
|
||||
if args.private_markers_file:
|
||||
data=json.loads(args.private_markers_file.read_text(encoding='utf-8'))
|
||||
markers=data.get('private_markers',[]) if isinstance(data,dict) else data
|
||||
if not isinstance(markers,list) or not all(isinstance(value,str) for value in markers):
|
||||
parser.error('标记清单应为字符串列表,或含 private_markers 列表的 JSON 对象')
|
||||
result=inspect(ROOT,markers)
|
||||
print(json.dumps(result,ensure_ascii=False,indent=2))
|
||||
raise SystemExit(0 if result['passed'] else 1)
|
||||
@@ -0,0 +1,22 @@
|
||||
"""从公开模板创建本地配置;已经存在的文件不会被覆盖。"""
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def initialize(root=ROOT):
|
||||
created = []
|
||||
for stem in ('bot_config', 'members_config', 'llm_config'):
|
||||
destination = root / (stem + '.json')
|
||||
try:
|
||||
with destination.open('x', encoding='utf-8', newline='\n') as output:
|
||||
output.write((root / (stem + '.example.json')).read_text(encoding='utf-8'))
|
||||
except FileExistsError:
|
||||
continue
|
||||
created.append(destination.name)
|
||||
return created
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
created = initialize()
|
||||
print('已生成:' + '、'.join(created) if created else '本地配置已存在,未覆盖。')
|
||||
Reference in New Issue
Block a user