Add active mode to SAST triage bot for high-confidence verdicts

What does this MR do and why?

Today the SAST triage bot (introduced in !236130 (merged) and made advisory in !245464 (merged)) only recommends: every Semgrep custom-rule finding pings the AppSec group (@gitlab-com/gl-security/appsec) and gets the full set of triage labels, even when Duo Chat's classifier is highly confident about the verdict. This MR lets the bot act on high-confidence classifications instead of just annotating them, per the Phase 2 issue.

At >= 80% confidence (VERDICT_ACTION_THRESHOLD):

  • Likely false positive: post the inline comment with dismissal wording instead of the AppSec ping (keeping the audit trail), then auto-resolve the discussion via the REST API, and apply no labels.
  • Likely true positive: keep the normal ping comment, add an escalation note, and apply appsec-sast::escalated so TriageRotation can prioritize it.

Everything else — uncertain verdicts, classifier errors, confidence below 80%, or the flag off — is byte-identical to current behavior.

Gating

Requires SAST_TRIAGE_ACTIONS_ENABLED=1 and the two existing flags, SAST_TRIAGE_ENABLED=1 and SAST_TRIAGE_COMMENT_ENABLED=1. The comment flag is a hard dependency, not just convention: the dismissal comment's audit trail is the existing sanitized recommendation block, which only renders when comments are on. The new variable defaults to "0" in .gitlab/ci/static-analysis.gitlab-ci.yml, so this MR lands inert.

Security

SastTriageClassifier now enforces its own ALLOWED_API_URLS allowlist in the constructor (only https://gitlab.com/api/v4, trailing slash tolerated), raising ArgumentError otherwise. It previously depended on SemgrepResultProcessor#perform_allowlist_check having already run, so a future direct instantiation could have sent CUSTOM_SAST_RULES_BOT_PAT to an arbitrary host. Specs confirm no HTTP request fires when construction is rejected. The dismissal and escalation comment bodies are static strings; no model-derived text is added outside the existing sanitized recommendation block.

Rollout / flipping the flag

Before setting SAST_TRIAGE_ACTIONS_ENABLED=1, pre-create the appsec-sast::escalated scoped label in gitlab-org — otherwise the API auto-creates an unstyled project label on first use. AppSec plans to sample auto-dismissals manually for the first few weeks after flipping the flag. Kill switches: set the project-level variable back to "0", or apply appsec-sast::stop to a specific MR. The 90-day gl-sast-triage-verdicts.json CI artifact plus the resolved discussion threads form the audit trail.

Test coverage

106 examples across spec/scripts/sast_triage_classifier_spec.rb and spec/scripts/semgrep_result_processor_spec.rb cover the threshold boundary, both action paths, the resolution-failure fail-open, label behavior across single and mixed-verdict MRs, dedupe via the fingerprint header, and a flag-off regression test.

References

Merge request reports

Loading
Loading