Files
ISEP/scripts/test-ticket-api-bypass-guard.sh
T
Leo 23e4472715 封「新增 Gitea 東西」的每一道側門,不是只有開票(inkstone/ISEP#72)
leo 2026-08-27:「開票不查是否有現成的,這是什麼問題?」「不只開票前,
所有新增 gitea 的東西,都要搜尋」。

v1 的 ticket-api-bypass-guard 只認裸字 `grep -qw POST`(大小寫敏感),
只管 issues 端點。2026-08-27 總管一天開 13 張票,v1 一次都沒攔到——
用的是 `urllib.request.Request(url, data=...)`(靠傳 data= 隱式變 POST,
指令裡從頭到尾沒有 "POST" 三個字)和 `requests.post(...)`(小寫)。

本次改動:
- 寫入訊號從單一裸字換成一組結構性訊號(curl 資料類旗標/`.post(`/
  `method=post`/urllib 隱式 POST),且大小寫不敏感,同時保留 v1 的裸字比對
- 端點擴大到 milestones/labels/pulls(含 org 層級的 labels),不再只管 issues
- 用「resource/數字」特徵先放行帶 ID 的既有資源子路徑(貼留言、改標籤、
  合併 PR 等),避免貪婪比對把 `/issues/5/labels` 誤判成頂層 `/labels` 端點
- 擋下 issues 新增時,順手用標題猜關鍵字查一次跨 repo 搜尋,把可能撞到的
  舊票(例如 Arcrun#100)直接列進擋下的訊息裡

測試:scripts/test-ticket-api-bypass-guard.sh 24/24 通過(v1 的 13 條 + 本次
新增 11 條,含一條打真實 Gitea 網路重演 Arcrun#100 重複主題偵測)。

plugin.json 0.5.0 → 0.5.1;version-consistency 會在合併打 tag 那一刻才對齊
(release-tag-guard.sh 的既定分工:打 tag 是總管驗過整個 milestone 之後的事)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 16:19:06 +08:00

89 lines
5.2 KiB
Bash
Executable File
Raw 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.
#!/bin/bash
# 開票側門閘的測試(docs/TESTING.md A4
# 判準:4 種該擋、8 種不該擋、1 種有戳記時放行 = 13 條(v1,仍須全過,不能為了
# 新案例把舊的改壞)+ inkstone/ISEP#72 擴大範圍後新增的案例(隱式/小寫 POST、
# milestonelabelPR、org 端點、Arcrun#100 重演)。
cd "$(dirname "$0")/.." || exit 1
H=hooks/ticket-api-bypass-guard.sh
PASS=0; FAIL=0
run(){ # $1=want $2=cmd
printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' "$2")}}" \
| bash "$H" >/dev/null 2>&1
got=$?
if [ "$got" = "$1" ]; then PASS=$((PASS+1)); printf ' ✅ '; else FAIL=$((FAIL+1)); printf ' ❌ '; fi
printf 'want=%s got=%s %.56s\n' "$1" "$got" "$2"
}
SAVED=""; [ -f /tmp/.ticket-where-ok ] && SAVED=$(cat /tmp/.ticket-where-ok)
rm -f /tmp/.ticket-where-ok
echo "── 該擋(沒有搜尋戳記,且真的在開新票)──"
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues -d @b.json'
run 2 'python3 -c "req(\"POST\", f\"{API}/repos/{REPO}/issues\", {\"title\":\"x\"})"'
run 2 'curl --request POST "$API/repos/inkstone/InkStoneCo/issues"'
run 2 'req("POST",f"{API}/repos/{REPO}/issues",{"title":"x","labels":[1]})'
echo "── 不該擋(誤攔比漏擋更該修)──"
run 0 'curl -s "https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues?state=open"'
run 0 'req("POST", f"{API}/repos/{REPO}/issues/14/comments", {"body":"x"})'
run 0 'req("POST", f"{API}/repos/{REPO}/issues/5/labels", {"labels":[1]})'
run 0 'req("PATCH", f"{API}/repos/{REPO}/issues/5", {"state":"closed"})'
run 0 'scripts/ticket new ISEP -F /tmp/b.md --title "x"'
run 0 'echo "等一下要開票到 /repos/x/issues"'
run 0 'grep -n issues hooks/ticket-api-bypass-guard.sh'
run 0 'curl -s "$API/repos/inkstone/Arcrun/issues?state=open&limit=100"'
echo "── 有新鮮戳記時放行 ──"
python3 -c "import json,time;json.dump({'at':time.time(),'n':0,'top':[]},open('/tmp/.ticket-where-ok','w'))"
run 0 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues'
rm -f /tmp/.ticket-where-ok
echo
echo "── ISEP#722026-08-27 實際撞到的繞法(v1 grep -qw POST 漏掉的),該擋 ──"
run 2 'url = "https://git.uncle6.me/api/v1/repos/inkstone/ISEP/issues"; req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers=h); urllib.request.urlopen(req)'
run 2 'python3 -c "requests.post(f\"{API}/repos/{REPO}/issues\", json={\"title\":\"x\"})"'
echo
echo "── ISEP#72:範圍擴大到 milestonelabelPR,該擋 ──"
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/milestones -d @m.json'
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/labels -d @l.json'
run 2 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/pulls -d @p.json'
run 2 'curl -X POST https://git.uncle6.me/api/v1/orgs/inkstone/labels -d @l.json'
echo
echo "── ISEP#72:帶 ID 的 milestonelabel/PR 子路徑,不該擋(改狀態/合併,不是新增)──"
run 0 'curl -X PATCH https://git.uncle6.me/api/v1/repos/inkstone/ISEP/milestones/3 -d @m.json'
run 0 'curl -X PATCH https://git.uncle6.me/api/v1/repos/inkstone/ISEP/labels/9 -d @l.json'
run 0 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/pulls/12/merge'
echo
echo "── ISEP#72:有戳記時,milestonelabelPR 也放行(不是只有 issues)──"
python3 -c "import json,time;json.dump({'at':time.time(),'n':0,'top':[]},open('/tmp/.ticket-where-ok','w'))"
run 0 'curl -X POST https://git.uncle6.me/api/v1/repos/inkstone/ISEP/milestones -d @m.json'
rm -f /tmp/.ticket-where-ok
echo
echo "── ISEP#72Arcrun#100 重演——標題撞同主題舊票,擋下來還要點名 ──"
echo " (這條打真實 Gitea 網路;離線環境會印 SKIP,不算失敗。標題故意用"
echo " 裸引號的 python dict 寫法,跟 2026-08-27 那 13 張票實際的寫法一致——"
echo " curl -d 那種「JSON 被跳脫成字串塞進另一層引號」的寫法,本閘的猜題功能"
echo " 刻意不支援,見 hook 檔頭「只認雙引號」那段註解,訊息裡不會列候選舊票,"
echo " 但擋下來這件事不受影響,另有獨立案例覆蓋。)"
if git remote -v 2>/dev/null | grep 'git\.uncle6\.me' | grep -q '@'; then
OUT=$(printf '%s' "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":$(python3 -c 'import json,sys;print(json.dumps(sys.argv[1]))' 'req("POST", f"{API}/repos/{REPO}/issues", {"title":"總圖說我一條關聯都沒有,三元組 graph 全部消失"})')}}" \
| bash "$H" 2>&1 >/dev/null)
RC=$?
if [ "$RC" = 2 ] && printf '%s' "$OUT" | grep -q 'Arcrun#100'; then
PASS=$((PASS+1)); echo " ✅ 擋下來(rc=2)且點名 Arcrun#100"
else
FAIL=$((FAIL+1)); echo " ❌ rc=$RC,訊息裡有沒有 Arcrun#100"; printf '%s\n' "$OUT" | grep -q 'Arcrun#100' && echo "有" || echo "沒有"
printf '%s\n' "$OUT"
fi
else
echo " ⏭️ SKIP(這台機器的 git remote 沒帶 git.uncle6.me 的憑證,連不上就不算數)"
fi
[ -n "$SAVED" ] && printf '%s' "$SAVED" > /tmp/.ticket-where-ok
echo
echo "$PASS/$((PASS+FAIL)) 通過"
[ "$FAIL" -eq 0 ]