Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1

# 发版时同步部署 Fumadocs 文档站到 GitHub Pages, 文档与 PyPI 版本一致。
# 用 if: always() 让文档部署不被 publish 阻塞(workflow_dispatch 重新部署文档时,
# publish 会因版本门禁失败(版本未变),但 deploy-pages 仍应运行)。
deploy-pages:
needs: publish
if: always()
runs-on: ubuntu-latest
environment:
name: github-pages
Expand Down
41 changes: 36 additions & 5 deletions scripts/open_source_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,30 @@ def to_dict(self) -> dict[str, object]:
pattern=re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
description="AWS-style access key IDs must not be published",
),
ContentRule(
name="aws-secret-access-key",
# AWS Secret Access Key: 40 字符 base64-ish,常含 / + =,赋值给 SECRET_KEY/secret_key
pattern=re.compile(r"(?i)\b(?:aws_secret_access_key|secret_access_key|secret_key)\b\s*[:=]\s*[\"']?[A-Za-z0-9/+=]{40}"),
description="AWS-style secret access keys must not be published",
),
ContentRule(
name="ksyun-access-key",
# 金山云 AK: AKLT 前缀 + 20 字符
pattern=re.compile(r"\bAKLT[A-Za-z0-9]{16,}\b"),
description="Kingsoft Cloud access keys (AKLT*) must not be published",
),
ContentRule(
name="ksyun-secret-key-assignment",
# 金山云 SK: 赋值给 KSYUN_SECRET_KEY/secret_key,值是 40 字符 base64-ish(常以 OHL/AKL 开头但不确定)
pattern=re.compile(r"(?i)\bksyun_secret_key\b\s*[:=]\s*[\"']?[A-Za-z0-9/+=]{32,}"),
description="Kingsoft Cloud secret keys must not be published",
),
ContentRule(
name="uuid-secret-assignment",
# UUID 格式 key 赋值给 *_API_KEY/*_TOKEN/*_MCP_KEY 等(如 OPENAI_API_KEY=4fd210b0-...)
pattern=re.compile(r"(?i)\b[A-Z0-9_]*(?:API_KEY|MCP_KEY|TOKEN|SECRET)[A-Z0-9_]*\b\s*[:=]\s*[\"']?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"),
description="UUID-shaped secrets assigned to *_KEY/*_TOKEN vars must not be published",
),
ContentRule(
name="openai-api-key",
pattern=re.compile(r"\bsk-(?:proj-)?[A-Za-z0-9_-]{40,}\b"),
Expand All @@ -251,15 +275,19 @@ def to_dict(self) -> dict[str, object]:
),
ContentRule(
name="long-lived-secret-assignment",
# 长串 secret 赋值:VAR=值 或 VAR: 值,值至少 24 字符,排除明显占位符。
# 收紧:值必须含数字或 /+=-(base64/UUID/十六进制特征),排除纯字母函数名/变量名误报
# (如 api_key = _resolve_workspace_command_context(...))。值后不能紧跟 ( (函数调用)。
pattern=re.compile(
r"(?i)\b(?:secret_key|access_key|api_key|token|password)\b\s*[:=]\s*"
r"(?i)\b(?:secret_key|access_key|api_key|mcp_key|token|password|passwd)\b\s*[:=]\s*"
r"[\"']?(?!"
r"dummy|test|fake|example|placeholder|"
r"dummy|test|fake|example|placeholder|<|your_|xxx|"
r"sk-test|sk-live|secret-token|secret-key|super-secret|skill-service|"
r"gateway-token-demo|stale-secret|cli-app-secret|secret-demo|"
r"my-secret-token|kdocs-test-token|mem0-secret"
r")"
r"(?:[A-Za-z0-9_./+=-]{24,})(?:[\"']|\s*$)"
r"(?=[A-Za-z0-9_./+=-]*[0-9/+=-])"
r"[A-Za-z0-9_./+=-]{24,}"
),
description="Long-lived secret-looking assignments must not be published",
),
Expand Down Expand Up @@ -341,11 +369,13 @@ def should_scan_text(path: str) -> bool:


def audit_file_contents(root: Path, paths: Iterable[str]) -> AuditResult:
# audit 自身测试文件含 secret 形态的 fixture(用于测规则),不应被报为违规。
ignored_files = {"tests/test_open_source_audit.py"}
checked = 0
violations: list[Violation] = []
for raw_path in paths:
normalized = normalize_path(raw_path)
if not normalized or not should_scan_text(normalized):
if not normalized or normalized in ignored_files or not should_scan_text(normalized):
continue

path = root / normalized
Expand All @@ -363,7 +393,8 @@ def audit_file_contents(root: Path, paths: Iterable[str]) -> AuditResult:
violations.append(
Violation(path=normalized, rule=rule.name, description=rule.description)
)
break
# 不 break:一个文件可能含多种 secret(如 .env 同时有 AWS/KSYUN/OpenAI key),
# 全部报出才能反映完整风险。

return AuditResult(
target="content",
Expand Down
Loading