fix(security): notify only on finding-state-change + PR runs report-only (stop Telegram/Matrix spam) #1

Merged
vr6syncro merged 4 commits from fix/notify-state-change-pr-report-only into main 2026-06-01 17:30:00 +02:00
Owner

Problem

The reusable security.yml and security-hardened.yml send Telegram + Matrix on every run where SECURITY_FINDINGS == 1 (gated only by severity floor + channel filter). An unchanged finding set therefore re-spams chat on every push / PR / daily cron across ~50 thin-caller repos. The issue create/upsert/auto-close logic was already correct (stable <!-- security-scan:v2 --> marker, PATCH-in-place, auto-close on resolution) — the defect was purely on the notify side, plus pull_request events ran the full issue+chat pipeline.

The 6 changes (applied to BOTH security.yml AND security-hardened.yml)

  1. Finding-state fingerprint. In Build security report, after the findings are assembled, compute SECURITY_FP = sha256 of the sorted unique finding lines (CVE-id:package rows from Trivy + a per-scanner section flag for node/pip/osv/misconfig/sast) and write it to GITHUB_ENV. Deterministic (sort -u before hashing).

  2. fp embedded in the issue marker. The marker becomes <!-- security-scan:v2 fp=<SECURITY_FP> -->. In Create or update Forgejo security issue, the existing marker-issue is fetched once, its stored fp is parsed before patching, and CHANGED=1 is set only if the issue is new OR the stored fp differs from the new fp (findings changed/escalated); else CHANGED=0. The issue body is still PATCHed in place regardless so it stays current (new fp included). All marker-search contains(...) clauses were loosened from the exact <!-- security-scan:v2 --> to the prefix <!-- security-scan:v2 so fp-marked issues still match (upsert + dedup + close).

  3. Notify gated on state-change. Both Notify Telegram and Notify Matrix now require && env.CHANGED == '1'. An unchanged finding set updates the issue silently and never re-pings chat.

  4. pull_request runs are report-only. The issue-upsert step, both notify steps, and the close step now also carry && github.event_name != 'pull_request'. PR runs scan + surface results in the run log only — no issues, no chat, no closes.

  5. Auto-close still works, notify once on resolution. The Close resolved... step counts how many issues it actually closes; when >= 1, it emits RESOLVED_NOTIFY=1 and builds a single ✅ Security findings resolved message. Two new steps (Notify Telegram (resolved) / Notify Matrix (resolved)) send it once. When nothing was open, it stays silent (RESOLVED_NOTIFY=0).

  6. Example cron daily → weekly. examples/security-caller.yml cron changed from 23 4 * * * to 23 4 * * 1 (Mondays) with a comment that push + PR already cover real changes — the weekly run is just a safety-net. README.md documents the new state-change-only notify behaviour, the fp marker, the resolve-once notify, and PR report-only. (The ~50 thin-caller repos are not edited here — the central reusable change already fixes notify for all of them; the cron is just the example default going forward.)

Preserved / non-goals

All existing scanner functionality (Trivy fs vuln/secret[/misconfig/license], pip-audit, Node audit, OSV-Scanner, Hadolint, optional SAST), secrets usage (MATRIX_*, TELEGRAM_*, GITHUB_TOKEN), SEC_NOTIFY_TARGETS, SEC_AUTO_CLOSE, notify_min_sev severity floors, the anti-stampede jitter, and the overall YAML structure are untouched. The change is additive/surgical (security.yml: +124/-11, hardened: +124/-11; scanner steps not modified). Each run: block passes bash -n; all three YAML files parse cleanly.

⚠️ Promotion required

Thin callers pin @v2, which is a lightweight git tag, not a branch. Merging this PR to main alone changes nothing fleet-wide — the v2 tag must be re-pointed to the merged commit for the fix to take effect across all ~50 repos. Suggested promote steps (after review + merge to main):

git fetch origin
git tag -f v2 origin/main     # move the v2 tag to the merged main HEAD
git push -f origin v2         # force-update the tag

(Repos pinned to @main pick it up immediately on next run; @v2 repos only after the tag move.)

🤖 Generated with Claude Code

## Problem The reusable `security.yml` and `security-hardened.yml` send Telegram + Matrix on **every** run where `SECURITY_FINDINGS == 1` (gated only by severity floor + channel filter). An **unchanged** finding set therefore re-spams chat on every push / PR / daily cron across ~50 thin-caller repos. The issue create/upsert/auto-close logic was already correct (stable `<!-- security-scan:v2 -->` marker, PATCH-in-place, auto-close on resolution) — the defect was purely on the **notify** side, plus `pull_request` events ran the full issue+chat pipeline. ## The 6 changes (applied to BOTH `security.yml` AND `security-hardened.yml`) 1. **Finding-state fingerprint.** In *Build security report*, after the findings are assembled, compute `SECURITY_FP = sha256` of the **sorted unique** finding lines (`CVE-id:package` rows from Trivy + a per-scanner section flag for node/pip/osv/misconfig/sast) and write it to `GITHUB_ENV`. Deterministic (`sort -u` before hashing). 2. **fp embedded in the issue marker.** The marker becomes `<!-- security-scan:v2 fp=<SECURITY_FP> -->`. In *Create or update Forgejo security issue*, the existing marker-issue is fetched once, its stored `fp` is parsed **before** patching, and `CHANGED=1` is set only if the issue is **new** OR the stored fp differs from the new fp (findings changed/escalated); else `CHANGED=0`. The issue body is still **PATCHed in place regardless** so it stays current (new fp included). All marker-search `contains(...)` clauses were loosened from the exact `<!-- security-scan:v2 -->` to the prefix `<!-- security-scan:v2` so fp-marked issues still match (upsert + dedup + close). 3. **Notify gated on state-change.** Both *Notify Telegram* and *Notify Matrix* now require `&& env.CHANGED == '1'`. An unchanged finding set updates the issue **silently** and never re-pings chat. 4. **`pull_request` runs are report-only.** The issue-upsert step, both notify steps, and the close step now also carry `&& github.event_name != 'pull_request'`. PR runs scan + surface results in the run log only — no issues, no chat, no closes. 5. **Auto-close still works, notify once on resolution.** The *Close resolved...* step counts how many issues it actually closes; when `>= 1`, it emits `RESOLVED_NOTIFY=1` and builds a single `✅ Security findings resolved` message. Two new steps (*Notify Telegram (resolved)* / *Notify Matrix (resolved)*) send it once. When nothing was open, it stays silent (`RESOLVED_NOTIFY=0`). 6. **Example cron daily → weekly.** `examples/security-caller.yml` cron changed from `23 4 * * *` to `23 4 * * 1` (Mondays) with a comment that push + PR already cover real changes — the weekly run is just a safety-net. `README.md` documents the new state-change-only notify behaviour, the fp marker, the resolve-once notify, and PR report-only. (The ~50 thin-caller repos are **not** edited here — the central reusable change already fixes notify for all of them; the cron is just the example default going forward.) ## Preserved / non-goals All existing scanner functionality (Trivy fs vuln/secret[/misconfig/license], pip-audit, Node audit, OSV-Scanner, Hadolint, optional SAST), secrets usage (`MATRIX_*`, `TELEGRAM_*`, `GITHUB_TOKEN`), `SEC_NOTIFY_TARGETS`, `SEC_AUTO_CLOSE`, `notify_min_sev` severity floors, the anti-stampede jitter, and the overall YAML structure are untouched. The change is additive/surgical (security.yml: +124/-11, hardened: +124/-11; scanner steps not modified). Each `run:` block passes `bash -n`; all three YAML files parse cleanly. ## ⚠️ Promotion required Thin callers pin `@v2`, which is a **lightweight git tag**, not a branch. **Merging this PR to `main` alone changes nothing fleet-wide** — the `v2` tag must be re-pointed to the merged commit for the fix to take effect across all ~50 repos. Suggested promote steps (after review + merge to `main`): ``` git fetch origin git tag -f v2 origin/main # move the v2 tag to the merged main HEAD git push -f origin v2 # force-update the tag ``` (Repos pinned to `@main` pick it up immediately on next run; `@v2` repos only after the tag move.) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(security): fp-gated notify (state-change only) + PR report-only + notify-once-on-resolve
Some checks failed
security-hardened.yml / fix(security): fp-gated notify (state-change only) + PR report-only + notify-once-on-resolve (push) Failing after 0s
security.yml / fix(security): fp-gated notify (state-change only) + PR report-only + notify-once-on-resolve (push) Failing after 0s
055b36d48f
fix(security-hardened): fp-gated notify (state-change only) + PR report-only + notify-once-on-resolve
Some checks failed
security-hardened.yml / fix(security-hardened): fp-gated notify (state-change only) + PR report-only + notify-once-on-resolve (push) Failing after 0s
security.yml / fix(security-hardened): fp-gated notify (state-change only) + PR report-only + notify-once-on-resolve (push) Failing after 0s
af34caad16
fix(example): weekly cron instead of daily (push+PR cover real changes)
Some checks failed
security-hardened.yml / fix(example): weekly cron instead of daily (push+PR cover real changes) (push) Failing after 0s
security.yml / fix(example): weekly cron instead of daily (push+PR cover real changes) (push) Failing after 0s
f8a5d242ff
docs: document state-change-only notify + fp marker + PR report-only
Some checks failed
security-hardened.yml / docs: document state-change-only notify + fp marker + PR report-only (push) Failing after 0s
security.yml / docs: document state-change-only notify + fp marker + PR report-only (push) Failing after 0s
security-hardened.yml / docs: document state-change-only notify + fp marker + PR report-only (pull_request) Failing after 0s
security.yml / docs: document state-change-only notify + fp marker + PR report-only (pull_request) Failing after 0s
security-hardened.yml / fix(security): notify only on finding-state-change + PR runs report-only (stop Telegram/Matrix spam) (pull_request) Failing after 0s
security.yml / fix(security): notify only on finding-state-change + PR runs report-only (stop Telegram/Matrix spam) (pull_request) Failing after 0s
b87e0df986
vr6syncro deleted branch fix/notify-state-change-pr-report-only 2026-06-01 17:30:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
vr6syncro/ci-workflows!1
No description provided.