Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions .github/workflows/security-gates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# 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.
Comment thread
SharedQA marked this conversation as resolved.
#
# 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.
#
# 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:
# 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
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.
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 ;;
*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)
# 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
Comment thread
SharedQA marked this conversation as resolved.

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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- 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
11 changes: 11 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
@@ -0,0 +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.
# 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
Loading