From 8d55f7b305f3cae264eccf751f9d2bad96e4f04a Mon Sep 17 00:00:00 2001 From: Kenan Salim Date: Tue, 23 Jun 2026 22:57:44 +0300 Subject: [PATCH 1/2] ci(security): security-gates workflow (split 2/4 of #1317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split out of #1317 per review. The security slice: - .github/workflows/security-gates.yml — blocking gates: TruffleHog (verified secrets), Semgrep SAST (report-only ratchet), cargo-audit + pip-audit (RUSTSEC/CVE), Trivy IaC+fs. Actions/images pinned to SHAs. - .trivyignore. Self-standing: runs the scanners directly (no Makefile), no docs/helm/release files. The unfixable airbyte-cdk transitive CVE waivers (nltk/cryptography) are included with their tracking references. Signed-off-by: Kenan Salim --- .github/workflows/security-gates.yml | 148 +++++++++++++++++++++++++++ .trivyignore | 7 ++ 2 files changed, 155 insertions(+) create mode 100644 .github/workflows/security-gates.yml create mode 100644 .trivyignore diff --git a/.github/workflows/security-gates.yml b/.github/workflows/security-gates.yml new file mode 100644 index 000000000..97359a7b6 --- /dev/null +++ b/.github/workflows/security-gates.yml @@ -0,0 +1,148 @@ +# Security gates — MANDATORY, BLOCKING. +# +# Every job here is a required status check on `main` (see +# scripts/ci/enforce-quality-gates.sh). None of these jobs may use +# `continue-on-error` or `allow_failure` semantics: a finding either gets +# fixed, or gets an explicit reviewed suppression in-repo +# (.trivyignore / .semgrepignore / audit.toml). Silent bypass is not a state. +# +# Rationale: both company reference pipelines (gitlab.constr.dev 931666, +# 929528) carry Trivy scans with allow_failure that have been red for days. +# The Constructor/Virtuozzo CI/CD Master Plan §2 mandates hard-fail gates. +# +# All third-party actions and the SAST container image are pinned to immutable +# commit SHAs / digests (supply-chain hygiene — this gate practises what it preaches). + +name: Security Gates + +on: + pull_request: + branches: [main] + workflow_dispatch: + schedule: + # Nightly deep sweep (full history / full repo), per Master Plan §2: + # PR runs scan only the diff; the cron run scans everything. + - cron: "0 1 * * *" + +permissions: + contents: read + +concurrency: + group: security-gates-${{ github.ref }} + cancel-in-progress: true + +jobs: + secrets-scan: + name: secrets-scan + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + # PR: fetch enough history to diff base..head. Cron: full history. + # String operands: numeric 0 is falsy in &&/|| so it would collapse to 50. + fetch-depth: ${{ github.event_name == 'schedule' && '0' || '50' }} + persist-credentials: false + - name: TruffleHog (verified secrets are a hard failure) + uses: trufflesecurity/trufflehog@1aa1871f9ae24a8c8a3a48a9345514acf42beb39 # v3.82.13 + with: + base: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || '' }} + head: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }} + extra_args: --results=verified,unknown + + sast: + name: sast + runs-on: ubuntu-latest + timeout-minutes: 20 + container: + image: semgrep/semgrep:1.131.0@sha256:6bd07d7b166b097e1384f41b94a62d8c8a26a4fff8713992c296e053310da01f + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Semgrep scan (report-only — ratchet to blocking once baseline triaged) + # report-only during ratchet-in: p/default surfaces pre-existing findings (JWT ValidateLifetime, + # Dockerfile USER, …) tracked in TAF for owner triage. Re-add --error to block once the baseline is clean. + run: semgrep scan --config p/default --exclude-rule generic.secrets.security.detected-generic-secret + + deps-audit: + name: deps-audit + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Install cargo-audit + uses: taiki-e/install-action@375e0c7f08a66b8c2ba7e7eef31a6f91043a81b0 # v2.44.38 + with: + tool: cargo-audit@0.22.2 # >=0.22 parses CVSS 4.0 advisories (RUSTSEC-2026-0124) + - name: Rust dependency audit (RUSTSEC advisories block) + working-directory: src/backend + # Waivers tracked in constructorfabric/insight#1339 — remove each as it is fixed: + # RUSTSEC-2023-0071 (rsa "Marvin", no upstream fix), RUSTSEC-2024-0436 (paste), + # RUSTSEC-2026-0173 (proc-macro-error2) — last two unmaintained, all transitive. + run: cargo audit --deny warnings --ignore RUSTSEC-2023-0071 --ignore RUSTSEC-2024-0436 --ignore RUSTSEC-2026-0173 + - name: Set up Python + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + with: + python-version: "3.12" + - name: Python dependency audit (known CVEs block) + run: | + pip install --quiet 'pip-audit==2.10.1' + rc=0 + # Transitive advisories we cannot fix from here — same suppression + # policy as the Rust audit above (fix, or tracked in-repo waiver). + # Every connector depends on airbyte-cdk (>=7.23.1,<8.0.0), whose + # latest 7.x (7.23.2) HARD-PINS nltk==3.9.1 and caps cryptography + # <47.0.0, so the upstream fixes (nltk 3.9.4, cryptography 48.0.1) are + # unreachable until airbyte-cdk bumps them. Waivers tracked in + # constructorfabric/insight#1339 — remove each as the CDK fixes it. + IGNORES=( + # nltk 3.9.1 (airbyte-cdk pin: nltk==3.9.1) + --ignore-vuln PYSEC-2026-96 + --ignore-vuln PYSEC-2026-97 + --ignore-vuln PYSEC-2026-98 + --ignore-vuln PYSEC-2026-99 + --ignore-vuln GHSA-rf74-v2fm-23pw + --ignore-vuln CVE-2026-33230 + --ignore-vuln CVE-2026-33231 + # cryptography 46.0.7 (airbyte-cdk cap: cryptography<47.0.0) + --ignore-vuln GHSA-537c-gmf6-5ccf + ) + # Audit every Python project manifest in the ingestion tree. + while IFS= read -r f; do + echo "::group::pip-audit $f" + case "$f" in + *requirements*.txt) pip-audit "${IGNORES[@]}" -r "$f" || rc=1 ;; + *pyproject.toml) (cd "$(dirname "$f")" && pip-audit "${IGNORES[@]}" .) || rc=1 ;; + esac + echo "::endgroup::" + done < <(find src/ingestion -name 'pyproject.toml' -o -name 'requirements*.txt' | grep -v node_modules) + exit $rc + + trivy-config: + name: trivy-config + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Trivy IaC & Dockerfile misconfiguration scan (CRITICAL/HIGH block) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: config + scan-ref: . + severity: CRITICAL,HIGH + exit-code: "1" + trivyignores: .trivyignore + - name: Trivy filesystem vulnerability scan (CRITICAL/HIGH block) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: fs + scan-ref: . + severity: CRITICAL,HIGH + exit-code: "1" + ignore-unfixed: true + trivyignores: .trivyignore diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 000000000..d32a1b563 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,7 @@ +# Documented trivy-config waivers — tracked in constructorfabric/insight#1340. +# The gate stays blocking for every other misconfiguration class; remove each +# line as the underlying issue is fixed. +AVD-DS-0002 # container runs as root — build/test/tooling images +AVD-DS-0029 # apt-get without --no-install-recommends — toolbox build image +AVD-KSV-0118 # frontend Deployment missing securityContext — prod hardening backlog +AVD-KSV-0014 # frontend Deployment root FS not read-only — prod hardening backlog From 978bed35b73f9573de587fefb41088748cc030da Mon Sep 17 00:00:00 2001 From: Kenan Salim Date: Wed, 24 Jun 2026 10:28:31 +0300 Subject: [PATCH 2/2] =?UTF-8?q?ci(security):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20no-silent-pass,=20full=20fetch=20depth,=20contract?= =?UTF-8?q?=20honesty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All four CodeRabbit findings verified against the code and applied: - deps-audit: count scanned manifests and FAIL if zero found. A gate that audits nothing (find/glob breaks, tree moves) was a silent bypass — exactly what this workflow's header forbids. - secrets-scan: fetch-depth 0 (was 50 on PRs). TruffleHog diffs base..head; a shallow clone drops the base commit on any PR longer than the depth, so the scan errors or scans incompletely. Correctness of a secrets gate > CI minutes. - header: document the Semgrep report-only ratchet as the ONE sanctioned non-blocking gate (owner + exit condition), so the 'no silent bypass' contract is honest rather than contradicted. (semgrep scan w/o --error exits 0 by design — intentional, now documented at the top, not just inline.) - .trivyignore: move rationale to its own comment line above each ID, so the IDs match regardless of Trivy's inline-comment handling (version-safe waivers). Verified: yaml parses; no-silent-pass logic exits 1 on zero manifests, 0 normally. Signed-off-by: Kenan Salim --- .github/workflows/security-gates.yml | 23 ++++++++++++++++++++--- .trivyignore | 12 ++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/security-gates.yml b/.github/workflows/security-gates.yml index 97359a7b6..eaebb10ba 100644 --- a/.github/workflows/security-gates.yml +++ b/.github/workflows/security-gates.yml @@ -6,6 +6,13 @@ # fixed, or gets an explicit reviewed suppression in-repo # (.trivyignore / .semgrepignore / audit.toml). Silent bypass is not a state. # +# DOCUMENTED EXCEPTION (ratchet-in): the `sast` (Semgrep) job is REPORT-ONLY for +# now — `semgrep scan` runs WITHOUT `--error`, so findings are surfaced but do +# not block. This is the single sanctioned non-blocking gate while the p/default +# baseline is triaged (owner: QA/TAF). Exit condition: add `--error` to the +# semgrep step to make it hard-fail once the baseline is clean. Every OTHER job +# here blocks. +# # Rationale: both company reference pipelines (gitlab.constr.dev 931666, # 929528) carry Trivy scans with allow_failure that have been red for days. # The Constructor/Virtuozzo CI/CD Master Plan §2 mandates hard-fail gates. @@ -39,9 +46,11 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - # PR: fetch enough history to diff base..head. Cron: full history. - # String operands: numeric 0 is falsy in &&/|| so it would collapse to 50. - fetch-depth: ${{ github.event_name == 'schedule' && '0' || '50' }} + # Full history: TruffleHog diffs base.sha..head.sha on PRs, and a + # shallow clone (e.g. 50) drops the base commit on any PR with more + # commits than the depth — the secrets scan then errors or scans + # incompletely. Correctness of a secrets gate beats the CI minutes. + fetch-depth: 0 persist-credentials: false - name: TruffleHog (verified secrets are a hard failure) uses: trufflesecurity/trufflehog@1aa1871f9ae24a8c8a3a48a9345514acf42beb39 # v3.82.13 @@ -111,7 +120,9 @@ jobs: --ignore-vuln GHSA-537c-gmf6-5ccf ) # Audit every Python project manifest in the ingestion tree. + n=0 while IFS= read -r f; do + n=$((n + 1)) echo "::group::pip-audit $f" case "$f" in *requirements*.txt) pip-audit "${IGNORES[@]}" -r "$f" || rc=1 ;; @@ -119,6 +130,12 @@ jobs: esac echo "::endgroup::" done < <(find src/ingestion -name 'pyproject.toml' -o -name 'requirements*.txt' | grep -v node_modules) + # A gate that audits nothing is a silent bypass — fail if the discovery + # found no manifests (tree moved / glob broke) rather than passing green. + if [ "$n" -eq 0 ]; then + echo "::error::no Python manifests found under src/ingestion — nothing audited; refusing to pass" + exit 1 + fi exit $rc trivy-config: diff --git a/.trivyignore b/.trivyignore index d32a1b563..00dafec82 100644 --- a/.trivyignore +++ b/.trivyignore @@ -1,7 +1,11 @@ # Documented trivy-config waivers — tracked in constructorfabric/insight#1340. # The gate stays blocking for every other misconfiguration class; remove each # line as the underlying issue is fixed. -AVD-DS-0002 # container runs as root — build/test/tooling images -AVD-DS-0029 # apt-get without --no-install-recommends — toolbox build image -AVD-KSV-0118 # frontend Deployment missing securityContext — prod hardening backlog -AVD-KSV-0014 # frontend Deployment root FS not read-only — prod hardening backlog +# container runs as root — build/test/tooling images +AVD-DS-0002 +# apt-get without --no-install-recommends — toolbox build image +AVD-DS-0029 +# frontend Deployment missing securityContext — prod hardening backlog +AVD-KSV-0118 +# frontend Deployment root FS not read-only — prod hardening backlog +AVD-KSV-0014