docs(web): 鎖住公開前端 env 範例拓樸 [skip ci]

This commit is contained in:
Your Name
2026-06-15 13:16:41 +08:00
parent 7529232fd9
commit 952c92888b
4 changed files with 108 additions and 9 deletions

View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""檢查公開前端 env 範例不可洩漏內網拓樸。"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
PRIVATE_IP_PATTERN = re.compile(
r"\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})\b"
)
RETIRED_PUBLIC_TOPOLOGY_KEYS = {
"NEXT_PUBLIC_HOST_IPS",
"NEXT_PUBLIC_K8S_VIP_INFO",
}
ENV_EXAMPLE_PATHS = (
Path("apps/web/.env.example"),
)
def _active_assignment(line: str) -> tuple[str, str] | None:
stripped = line.strip()
if not stripped or stripped.startswith("#") or "=" not in stripped:
return None
key, value = stripped.split("=", 1)
return key.strip(), value.strip()
def validate(root: Path) -> None:
errors: list[str] = []
for relative_path in ENV_EXAMPLE_PATHS:
path = root / relative_path
if not path.exists():
continue
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
assignment = _active_assignment(line)
if assignment is None:
continue
key, value = assignment
if key in RETIRED_PUBLIC_TOPOLOGY_KEYS:
errors.append(f"{relative_path}:{line_number}: 已停用公開前端拓樸 env key{key}")
if PRIVATE_IP_PATTERN.search(value):
errors.append(f"{relative_path}:{line_number}: env 範例不得包含內網 IP{key}")
if key.startswith("NEXT_PUBLIC_") and not value:
errors.append(f"{relative_path}:{line_number}: NEXT_PUBLIC_* 範例需明確使用公網入口或安全預設值:{key}")
if errors:
raise SystemExit("BLOCKED public frontend env guard:\n" + "\n".join(f"- {error}" for error in errors))
def main() -> None:
parser = argparse.ArgumentParser(description="檢查公開前端 env 範例不可洩漏內網拓樸")
parser.add_argument("--root", default=".", help="repository root")
args = parser.parse_args()
validate(Path(args.root).resolve())
print("OK public frontend env guard")
if __name__ == "__main__":
main()

View File

@@ -83,6 +83,10 @@ def validate(root: Path) -> None:
str(root / "scripts" / "security" / "package-supply-chain-owner-policy-guard.py")
)
package_policy_guard["validate"](root)
public_frontend_env_guard = runpy.run_path(
str(root / "scripts" / "security" / "public-frontend-env-guard.py")
)
public_frontend_env_guard["validate"](root)
manifest = load_json(security_dir / "security-supply-chain-contract-manifest.snapshot.json")
readiness = load_json(security_dir / "security-mirror-readiness.snapshot.json")