Add Level 0 + Level 1 + Level 2 security scanning #12
@@ -154,6 +154,22 @@ jobs:
|
|||||||
blocking = [f for f in findings if f["severity"] in policy["block_on"] and f["confidence"] >= policy["min_confidence"]]
|
blocking = [f for f in findings if f["severity"] in policy["block_on"] and f["confidence"] >= policy["min_confidence"]]
|
||||||
blocked = len(blocking) > 0
|
blocked = len(blocking) > 0
|
||||||
|
|
||||||
|
# Один и тот же коммит может быть провёрен дважды (см. баг
|
||||||
|
# Gitea с переиспользованием SHA между разными PR — два
|
||||||
|
# параллельных прогона на идентичный коммит). Статус в
|
||||||
|
# Gitea хранит только ПОСЛЕДНЮЮ запись по (sha, context),
|
||||||
|
# так что более поздний, но менее строгий результат тихо
|
||||||
|
# затирает более ранний блокирующий. Раз уже была
|
||||||
|
# зафиксирована блокировка на этом коммите — не снимаем
|
||||||
|
# её молча, только новым коммитом.
|
||||||
|
prior_failed = False
|
||||||
|
try:
|
||||||
|
prior = gitea("GET", f"/repos/{REPO}/commits/{COMMIT_SHA}/statuses")
|
||||||
|
prior_failed = any(s.get("context") == "security/scan" and s.get("status") == "failure" for s in prior)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"не удалось прочитать прежние статусы, продолжаю без sticky-fail: {e}")
|
||||||
|
sticky = prior_failed and not blocked
|
||||||
|
|
||||||
outdir = f"security-analysis/findings/pr-{PR_NUMBER or 'manual'}"
|
outdir = f"security-analysis/findings/pr-{PR_NUMBER or 'manual'}"
|
||||||
os.makedirs(outdir, exist_ok=True)
|
os.makedirs(outdir, exist_ok=True)
|
||||||
outpath = f"{outdir}/{int(time.time())}.json"
|
outpath = f"{outdir}/{int(time.time())}.json"
|
||||||
@@ -177,19 +193,22 @@ jobs:
|
|||||||
for f in top:
|
for f in top:
|
||||||
body += f"**{f['severity'].upper()}** `{f['file']}:{f['line_start']}` — {f['title']} ({f['source']})\n\n"
|
body += f"**{f['severity'].upper()}** `{f['file']}:{f['line_start']}` — {f['title']} ({f['source']})\n\n"
|
||||||
body += f"Полный список: `{outpath}`\n\n"
|
body += f"Полный список: `{outpath}`\n\n"
|
||||||
|
if sticky:
|
||||||
|
body += "🔴 **Merge заблокирован** — этот же коммит уже был помечен как блокирующий в более раннем прогоне; новый прогон нашёл меньше, но старую блокировку это не снимает (нужен новый коммит).\n"
|
||||||
|
else:
|
||||||
body += "🔴 **Merge заблокирован** — есть находки выше порога `security-analysis/policy.yml`.\n" if blocked else "✅ Порог не превышен.\n"
|
body += "🔴 **Merge заблокирован** — есть находки выше порога `security-analysis/policy.yml`.\n" if blocked else "✅ Порог не превышен.\n"
|
||||||
|
|
||||||
gitea("POST", f"/repos/{REPO}/issues/{PR_NUMBER}/comments", {"body": body})
|
gitea("POST", f"/repos/{REPO}/issues/{PR_NUMBER}/comments", {"body": body})
|
||||||
|
|
||||||
gitea("POST", f"/repos/{REPO}/statuses/{COMMIT_SHA}", {
|
gitea("POST", f"/repos/{REPO}/statuses/{COMMIT_SHA}", {
|
||||||
"state": "failure" if blocked else "success",
|
"state": "failure" if (blocked or sticky) else "success",
|
||||||
"context": "security/scan",
|
"context": "security/scan",
|
||||||
"description": f"{len(findings)} findings, {len(blocking)} blocking",
|
"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}",
|
"target_url": f"{SERVER}/{REPO}/src/branch/{HEAD_REF}/{outpath}",
|
||||||
})
|
})
|
||||||
|
|
||||||
print(f"findings: {len(findings)}, blocking: {len(blocking)}")
|
print(f"findings: {len(findings)}, blocking: {len(blocking)}, sticky: {sticky}")
|
||||||
if blocked:
|
if blocked or sticky:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|
||||||
@@ -440,6 +459,20 @@ jobs:
|
|||||||
blocking = [f for f in findings if f["severity"] in policy["block_on"] and f["confidence"] >= policy["min_confidence"]]
|
blocking = [f for f in findings if f["severity"] in policy["block_on"] and f["confidence"] >= policy["min_confidence"]]
|
||||||
blocked = len(blocking) > 0
|
blocked = len(blocking) > 0
|
||||||
|
|
||||||
|
# См. аналогичный комментарий в шаге scan: LLM-ревьюер не
|
||||||
|
# детерминирован, а один коммит может быть провёрен дважды
|
||||||
|
# параллельно (баг Gitea с переиспользованием SHA между
|
||||||
|
# PR). Если более ранний прогон на ЭТОМ ЖЕ коммите уже
|
||||||
|
# нашёл блокирующую находку, более поздний "пустой" ответ
|
||||||
|
# модели не должен тихо снимать блокировку.
|
||||||
|
prior_failed = False
|
||||||
|
try:
|
||||||
|
prior = gitea("GET", f"/repos/{REPO}/commits/{COMMIT_SHA}/statuses")
|
||||||
|
prior_failed = any(s.get("context") == "security/review" and s.get("status") == "failure" for s in prior)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"не удалось прочитать прежние статусы, продолжаю без sticky-fail: {e}")
|
||||||
|
sticky = prior_failed and not blocked
|
||||||
|
|
||||||
outdir = f"security-analysis/findings/pr-{PR_NUMBER or 'manual'}"
|
outdir = f"security-analysis/findings/pr-{PR_NUMBER or 'manual'}"
|
||||||
outpath = f"{outdir}/{int(time.time())}-review.json"
|
outpath = f"{outdir}/{int(time.time())}-review.json"
|
||||||
|
|
||||||
@@ -467,19 +500,22 @@ jobs:
|
|||||||
if f.get("exploit_scenario"):
|
if f.get("exploit_scenario"):
|
||||||
body += f" сценарий: {f['exploit_scenario']}\n\n"
|
body += f" сценарий: {f['exploit_scenario']}\n\n"
|
||||||
body += f"Полный список: `{outpath}`\n\n"
|
body += f"Полный список: `{outpath}`\n\n"
|
||||||
|
if sticky:
|
||||||
|
body += "🔴 **Merge заблокирован** — этот же коммит уже был помечен как блокирующий в более раннем прогоне ревью; новый прогон ничего не нашёл, но старую блокировку это не снимает (нужен новый коммит).\n"
|
||||||
|
else:
|
||||||
body += "🔴 **Merge заблокирован** — есть находки выше порога `security-analysis/policy.yml`.\n" if blocked else "✅ Порог не превышен.\n"
|
body += "🔴 **Merge заблокирован** — есть находки выше порога `security-analysis/policy.yml`.\n" if blocked else "✅ Порог не превышен.\n"
|
||||||
|
|
||||||
gitea("POST", f"/repos/{REPO}/issues/{PR_NUMBER}/comments", {"body": body})
|
gitea("POST", f"/repos/{REPO}/issues/{PR_NUMBER}/comments", {"body": body})
|
||||||
|
|
||||||
gitea("POST", f"/repos/{REPO}/statuses/{COMMIT_SHA}", {
|
gitea("POST", f"/repos/{REPO}/statuses/{COMMIT_SHA}", {
|
||||||
"state": "failure" if blocked else "success",
|
"state": "failure" if (blocked or sticky) else "success",
|
||||||
"context": "security/review",
|
"context": "security/review",
|
||||||
"description": f"{len(findings)} findings, {len(blocking)} blocking",
|
"description": (f"{len(findings)} findings, {len(blocking)} blocking" + (" (sticky: prior run flagged this commit)" if sticky else "")),
|
||||||
"target_url": f"{SERVER}/{REPO}",
|
"target_url": f"{SERVER}/{REPO}",
|
||||||
})
|
})
|
||||||
|
|
||||||
print(f"findings: {len(findings)}, blocking: {len(blocking)}")
|
print(f"findings: {len(findings)}, blocking: {len(blocking)}, sticky: {sticky}")
|
||||||
if blocked:
|
if blocked or sticky:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
PYEOF2
|
PYEOF2
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user