Files

122 lines
5.1 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 外部事实登记表逐条重验:对 policy/facts.md 每条事实执行其验证命令并报告 PASS/FAIL。
# 规则依据:AGENTS.md「证据与验证门禁」(外部事实零信任);verification.md「声明与证据来源」。
# 用法:make facts-verify [ARGS=--update] (--update:把 PASS 项 last_verified 刷为当天并写回)
# 表内验证命令约定不含竖线;含 "\|" 转义时按字面还原后执行。
# 登记表引用的探针子命令(单独运行时在 stderr 说明判定,不输出凭据):
# api-token URL URL 须逐字等于登记端点 https://git-home.u1680832.nyat.app:30319/api/v1/user,
# 校验通过后才进程内读 ~/.secrets/gitea HOME 段 ACCESS_TOKEN 并 GET(不跟随重定向);
# 0=接受 3=缺失 4=格式不符 5=请求失败/被拒/重定向 6=非登记端点
# tls-handshake-fails HOST PORT 仅当 TCP 已连通且 TLS 层握手报错时 0;
# 3=DNS 失败 4=超时 5=连接失败 6=握手成功(端点仍可用)
set -uo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
case "${1:-}" in
api-token)
[ $# -eq 2 ] || { echo "usage: facts-verify.sh api-token URL" >&2; exit 2; }
exec python3 - "$SCRIPT_DIR" "$2" <<'PY'
import sys, urllib.request
sys.path.insert(0, sys.argv[1])
import task_triage
ENDPOINT = "https://git-home.u1680832.nyat.app:30319/api/v1/user"
if sys.argv[2] != ENDPOINT:
print("api-token: URL 不是 policy/facts.md F2 登记端点,拒绝发送凭据", file=sys.stderr)
sys.exit(6)
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
return None
urllib.request.install_opener(urllib.request.build_opener(NoRedirect))
url = ENDPOINT
token = task_triage.secrets_file_token()
if not token:
print("api-token: ~/.secrets/gitea HOME 段 ACCESS_TOKEN 缺失", file=sys.stderr)
sys.exit(3)
if not task_triage.TOKEN_RE.fullmatch(token):
print("api-token: ~/.secrets/gitea HOME 段 ACCESS_TOKEN 格式不符(应为单个无空白值)", file=sys.stderr)
sys.exit(4)
try:
task_triage.http_get_json(url, token)
except task_triage.Stop as stop:
print("api-token: " + stop.message, file=sys.stderr)
sys.exit(5)
print("api-token: accepted by " + url, file=sys.stderr)
PY
;;
tls-handshake-fails)
[ $# -eq 3 ] || { echo "usage: facts-verify.sh tls-handshake-fails HOST PORT" >&2; exit 2; }
exec python3 - "$2" "$3" <<'PY'
import socket, ssl, sys
host, port = sys.argv[1], int(sys.argv[2])
def verdict(code, text):
print(f"tls-handshake-fails {host}:{port}: {text}", file=sys.stderr)
sys.exit(code)
try:
sock = socket.create_connection((host, port), timeout=8)
except socket.gaierror:
verdict(3, "DNS resolution failed (not a TLS observation)")
except TimeoutError:
verdict(4, "TCP connect timed out (not a TLS observation)")
except OSError as exc:
verdict(5, f"TCP connect failed ({type(exc).__name__}; not a TLS observation)")
try:
with sock, ssl.create_default_context().wrap_socket(sock, server_hostname=host) as tls:
version = tls.version()
except ssl.SSLError as exc:
verdict(0, f"TLS handshake failed ({type(exc).__name__}: {getattr(exc, 'reason', None) or 'unknown'})")
except TimeoutError:
verdict(4, "TLS handshake timed out (not a TLS-layer error)")
except OSError as exc:
verdict(5, f"connection failed during handshake ({type(exc).__name__}; not a TLS-layer error)")
verdict(6, f"TLS handshake succeeded ({version}); endpoint still answers")
PY
;;
esac
F="${FACTS:-policy/facts.md}"
[ -f "$F" ] || { echo "ERROR: 事实登记表 $F 不存在" >&2; exit 1; }
python3 - "$F" "${1:-}" <<'PY'
import re, subprocess, sys, datetime
path, update = sys.argv[1], sys.argv[2] == "--update"
lines = open(path, encoding="utf-8").read().splitlines(keepends=True)
today = datetime.date.today().isoformat()
total = fails = 0
for i, line in enumerate(lines):
if not re.match(r"^\|\s*F\d+\s*\|", line):
continue
raw = line.strip().strip("|").replace("\\|", "\x00")
cells = [c.strip().replace("\x00", "|") for c in raw.split("|")]
if len(cells) < 4:
print(f"[row {i+1}] SKIP 格式不符(列数<4)", file=sys.stderr)
continue
fid, cmd = cells[0], cells[2].strip("`")
total += 1
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=90)
ok = r.returncode == 0
except subprocess.TimeoutExpired:
ok = False
print(f"[{fid}] TIMEOUT >90s", file=sys.stderr)
print(f"[{fid}] {'PASS' if ok else 'FAIL'} :: {cmd[:80]}")
if not ok:
fails += 1
elif update and len(cells) >= 4 and re.match(r"\d{4}-\d{2}-\d{2}", cells[3]):
cells[3] = today
lines[i] = "| " + " | ".join(c.replace("|", "\\|") for c in cells) + " |\n"
if update:
open(path, "w", encoding="utf-8").writelines(lines)
print(f"[facts-verify] total={total} pass={total - fails} fail={fails}"
+ (";last_verified 已刷新为 " + today if update and total else ""))
sys.exit(1 if fails else 0)
PY