From dc1066e20845a4e7ad43b7245e743db37baf7bb7 Mon Sep 17 00:00:00 2001 From: testclient-admin Date: Tue, 25 Aug 2026 03:08:07 +0000 Subject: [PATCH] Level 0: security scanning for pull requests --- .gitea/workflows/security.yml | 46 ++++++++++++----------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml index 863a79b..14aa682 100644 --- a/.gitea/workflows/security.yml +++ b/.gitea/workflows/security.yml @@ -64,17 +64,15 @@ jobs: REPO_FULL: ${{ gitea.repository }} PR_NUMBER: ${{ gitea.event.pull_request.number }} COMMIT_SHA: ${{ gitea.event.pull_request.head.sha }} - HEAD_REF: ${{ gitea.head_ref }} run: | python3 <<'PYEOF' - import base64, json, os, sys, time, urllib.error, urllib.request + import json, os, sys, time, urllib.error, urllib.request SERVER = os.environ["GITEA_SERVER"].rstrip("/") REPO = os.environ["REPO_FULL"] TOKEN = os.environ["SECURITY_BOT_TOKEN"] PR_NUMBER = os.environ.get("PR_NUMBER", "") COMMIT_SHA = os.environ["COMMIT_SHA"] - HEAD_REF = os.environ["HEAD_REF"] def gitea(method, path, payload=None): req = urllib.request.Request( @@ -170,17 +168,17 @@ jobs: print(f"не удалось прочитать прежние статусы, продолжаю без sticky-fail: {e}") sticky = prior_failed and not blocked - outdir = f"security-analysis/findings/pr-{PR_NUMBER or 'manual'}" - os.makedirs(outdir, exist_ok=True) - outpath = f"{outdir}/{int(time.time())}.json" - json.dump(findings, open(outpath, "w"), ensure_ascii=False, indent=2) - - gitea("POST", f"/repos/{REPO}/contents/{outpath}", { - "content": base64.b64encode(open(outpath, "rb").read()).decode(), - "message": f"security: scan results for PR #{PR_NUMBER}", - "branch": HEAD_REF, - }) - + # Раньше находки ещё и коммитились в ветку PR. Плохая + # идея с точки зрения git-гигиены: чужая ветка растёт + # коммитами от бота, каждый такой коммит сам порождает + # pull_request:synchronize (нужна отдельная защита от + # петли), а когда коммит бота становится головой PR — + # обязательные security/scan и security/review больше + # никогда не выставляются на этой голове, потому что + # job их специально пропускает для бота же — PR + # перестаёт мержиться насовсем (поймано живьём). + # Комментарий в PR (ниже) и так несёт всё нужное + # человеку; отдельного коммита с JSON не требуется. counts = {} for f in findings: counts[f["severity"]] = counts.get(f["severity"], 0) + 1 @@ -192,7 +190,6 @@ jobs: body += "\n\n" for f in top: body += f"**{f['severity'].upper()}** `{f['file']}:{f['line_start']}` — {f['title']} ({f['source']})\n\n" - body += f"Полный список: `{outpath}`\n\n" if sticky: body += "🔴 **Merge заблокирован** — этот же коммит уже был помечен как блокирующий в более раннем прогоне; новый прогон нашёл меньше, но старую блокировку это не снимает (нужен новый коммит).\n" else: @@ -204,7 +201,6 @@ jobs: "state": "failure" if (blocked or sticky) else "success", "context": "security/scan", "description": (f"{len(findings)} findings, {len(blocking)} blocking" + (" (sticky: prior run flagged this commit)" if sticky else "")), - "target_url": f"{SERVER}/{REPO}/src/branch/{HEAD_REF}/{outpath}", }) print(f"findings: {len(findings)}, blocking: {len(blocking)}, sticky: {sticky}") @@ -363,17 +359,15 @@ jobs: REPO_FULL: ${{ gitea.repository }} PR_NUMBER: ${{ gitea.event.pull_request.number }} COMMIT_SHA: ${{ gitea.event.pull_request.head.sha }} - HEAD_REF: ${{ gitea.head_ref }} run: | python3 <<'PYEOF2' - import base64, json, os, sys, time, urllib.error, urllib.request + import json, os, sys, time, urllib.error, urllib.request SERVER = os.environ["GITEA_SERVER"].rstrip("/") REPO = os.environ["REPO_FULL"] TOKEN = os.environ["SECURITY_BOT_TOKEN"] PR_NUMBER = os.environ.get("PR_NUMBER", "") COMMIT_SHA = os.environ["COMMIT_SHA"] - HEAD_REF = os.environ["HEAD_REF"] def gitea(method, path, payload=None): req = urllib.request.Request( @@ -473,16 +467,9 @@ jobs: print(f"не удалось прочитать прежние статусы, продолжаю без sticky-fail: {e}") sticky = prior_failed and not blocked - outdir = f"security-analysis/findings/pr-{PR_NUMBER or 'manual'}" - outpath = f"{outdir}/{int(time.time())}-review.json" - - if findings: - gitea("POST", f"/repos/{REPO}/contents/{outpath}", { - "content": base64.b64encode(json.dumps(findings, ensure_ascii=False, indent=2).encode()).decode(), - "message": f"security: AI review results for PR #{PR_NUMBER}", - "branch": HEAD_REF, - }) - + # Коммит с findings-json в ветку PR убран — та же причина, + # что и в шаге scan: он ломает мержимость PR насовсем, + # как только становится головой (см. комментарий там). counts = {} for f in findings: counts[f["severity"]] = counts.get(f["severity"], 0) + 1 @@ -499,7 +486,6 @@ jobs: body += f"**{f['severity'].upper()}** (`{f['confidence']:.2f}`) `{f['file']}:{f['line_start']}` — {f['title']}\n\n" if f.get("exploit_scenario"): body += f" сценарий: {f['exploit_scenario']}\n\n" - body += f"Полный список: `{outpath}`\n\n" if sticky: body += "🔴 **Merge заблокирован** — этот же коммит уже был помечен как блокирующий в более раннем прогоне ревью; новый прогон ничего не нашёл, но старую блокировку это не снимает (нужен новый коммит).\n" else: